2012-07-19 21 views
5

Używam zewnętrznego komponentu, który okresowo uruchamia zdarzenia z wątku roboczego. W moim programie obsługi zdarzeń używam Dispatchera do wywołania metody w głównym wątku. Działa to ładnie ...Zakleszczenie, gdy wątek roboczy próbuje wywołać coś w głównym wątku

private void HandleXYZ(object sender, EventArgs e) 
{ 
    ... 
    if(OnTrigger != null) 
     dispatcher.Invoke(OnTrigger, new TimeSpan(0, 0, 1), e); 
} 

Jednak, gdy program zostanie zamknięty, a zewnętrzny komponent Dispose() s, programu czasami zawiesza się (i można zobaczyć i zabity w menedżerze zadań tylko).

Kiedy patrzę na to, co się dzieje, wygląda na to, że "komponent" czeka na powrót zdarzenia w wątku głównym (pozostaje w metodzie Dispose()), podczas gdy wątek roboczy oczekuje na wywołanie przez dyspozytora wspomniane wywołanie do głównego wątku (zawiesza się w linii dispatcher.Invoke-line).

Na razie rozwiązałem problem z zamykaniem systemu przez dodanie limitu czasu do Invoke, który wydaje się działać, ale czuje się źle. Czy istnieje lepszy sposób na zrobienie czegoś takiego? Czy mogę wymusić, aby główny wątek zabrał trochę czasu na zadania z innych wątków przed zamknięciem?

Próbowałem „Disconnect” zdarzenie przed zamknięciem, ale to nie pomaga, bo dyspozytor jest (może być) już czeka, kiedy program rozpocznie się zamknąć ...

PS: Komponent zewnętrzny oznacza tutaj, że nie mam dostępu do kodu źródłowego ...

+0

Proszę używać akapity następnym razem – Shai

+1

Wklejenie kodu będzie bardzo pomóc – Vedran

Odpowiedz

6

Tak, jest to częste źródło zakleszczenia. Zawiesza się, ponieważ dyspozytor opuścił pętlę dyspozytora, który nie będzie już odpowiadać na żądania Invoke. Szybkim lekarstwem jest użycie BeginInvoke zamiast tego, nie czeka na zakończenie zadania invoke. Kolejny szybki numerek to ustawienie właściwości IsBackground wątku roboczego na True, aby CLR ją zabił.

Są to szybkie poprawki, które mogą Ci pomóc. Z pewnością na twoim komputerze deweloperskim, ale jeśli masz dokuczliwe uczucie, że nadal może się nie udać, masz rację, nie przestrzegając zakleszczenia ani nie tocząc wyścigu, , a nie udowodnić, że nie są obecne. Istnieją dwa „dobre” sposoby to zrobić całkowicie bezpiecznie:

  • nie pozwalają główny wątek, aby wyjść, dopóki nie są pewien że wątek pracownik zakończone i nie może podnieść zdarzenia. This answer pokazuje wzór.

  • zakończyć program z użyciem Environment.Exit().Jest to bardzo skuteczne, ale skuteczne, młotek, do którego dojdziesz tylko wtedy, gdy masz program z grubym gwintem, w którym wątek UI jest tylko drugim obywatelem. Dziwne, ponieważ może to brzmieć jako odpowiednie podejście, nowy standard języka C++ podniósł go do obsługiwanego sposobu zakończenia programu. Możesz przeczytać więcej na ten temat w this answer. Zanotuj, w jaki sposób można zarejestrować funkcje czyszczenia, będziesz musiał zrobić coś podobnego za pomocą, powiedzmy, zdarzenia AppDomain.ProcessExit. Skoncentruj się na pierwszym pocisku, zanim to zrobisz.

+0

Dobra odpowiedź! Powiedziałbym, że sposób BeginInvoke jest lepszy niż czas oczekiwania. – FrankB

+0

Czy to oznacza: jeśli program nie został wyłączony, a wątek główny czekał na zdarzenie ... Czy dyspozytor przeprowadzi wywołanie do głównego wątku, chociaż wątek główny "aktywnie" czeka? – FrankB

+0

Nie wiem, jak to jest związane z pierwotnym pytaniem. Ale każdy rodzaj "czekania" w głównym wątku prawdopodobnie spowoduje również impas. Wywołanie Invoke() można wywołać tylko wtedy, gdy główny wątek jest bezczynny i wykonuje pętlę dyspozytora. –

1

Co do subskrypcji zdarzeń, to naprawdę dobry pomysł, aby je wyczyścić, gdy wiesz, że dany obiekt nie jest już potrzebny. W przeciwnym razie ryzykowałbyś powstawaniem wycieków pamięci. Możesz również rzucić okiem na weak event pattern (MSDN).

Odnośnie samego impasu, bez znajomości kodu, możemy tylko zgadywać.

Nie widzę HandleXYZ() jako winowajcy, wolałbym sprawdzić swoją implementację IDisposable(). Spójrz na MSDN documentation i porównaj to ze swoją implementacją.

Przypuszczam, że gdzieś w twojej implementacji są wykonywane wywołania metod, które zależą od czasu GarbageCollector, który jest indeterministyczny: Czasami może się to udać w twoim przypadku, czasami może nie.

+0

Dzięki ... ale niestety kod Dispose() nie jest dostępna do mnie (komponent zewnętrzny) – FrankB

+1

Pytanie, @FrankB, czy wykonujesz jakieś porządki? Zasadą jest, że ZAWSZE usuwam każdą klasę, która implementuje 'IDispoeable()'. Jeśli ten komponent innej firmy czeka na coś, może się zdarzyć, że twój własny kod nie zwolnił właściwie niektórych zasobów. –

+0

Ten inny komponent czeka na powrót zdarzenia, podczas gdy mój moduł obsługi zdarzeń czeka na główny wątek (który jest blokowany przez komponent oczekiwania ...). Sprzątanie jest tylko rozwiązaniem, jeśli mogę zmusić dyspozytora, by przestał czekać na główny wątek. Czy mogę to zrobić poza ustawianiem limitu czasu? – FrankB

Powiązane problemy