2008-11-05 8 views
21

Mam dość skomplikowaną aplikację WPF, która wydaje się "zawieszać" lub utknąć w wywołaniu Czekaj, próbując użyć dyspozytora do wywołania wywołania w wątku UI.Dyspozytor WPF. Wywołanie "zawieszania"

Ogólny proces jest:

  1. obsłużyć zdarzenia kliknij na przycisku
  2. Tworzenie nowego wątku (STA), który: tworzy nową instancję prezenter i interfejsu użytkownika, a następnie wywołuje metodę Disconnect
  3. Disconnect następnie ustawia właściwość na interfejsie o nazwie Nazwa
  4. setter dla Name następnie wykorzystuje poniższy kod, żeby ustawić właściwość:

    if(this.Dispatcher.Thread != Thread.CurrentThread) 
    { 
     this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{ 
      this.Name = value; // Call same setter, but on the UI thread 
     }); 
     return; 
    } 

    SetValue(nameProperty, value); // I have also tried a member variable and setting the textbox.text property directly. 

Moim problemem jest to, że gdy dyspozytor powołać wywoływana jest metoda wydaje się powiesić za każdym razem, a callstack wskazuje, że w jego śnie, czekać lub dołączyć w ramach realizacji Invoke.

Czy jest coś, co robię źle, czego mi brakuje, oczywiste czy nie, czy też istnieje lepszy sposób wywoływania do wątku interfejsu użytkownika, aby ustawić tę właściwość (i inne)?

Edit: Rozwiązaniem było zadzwonić System.Windows.Threading.Dispatcher.Run() na końcu delegata gwintu (np którym praca była wykonywana) - Dziękuję wszystkim, którzy pomogli.

+0

@Matthew - właściwie nie ma nic "nieoptymalnego" w BeginInvoke; jeśli nie potrzebujesz absolutnie aktualizacji * teraz *, to jest w porządku. Musisz jednak zachować ostrożność przy przechwytywaniu zmiennych (tzn. Nie zmieniaj "wartości" po wywołaniu BeginInvoke) W ogóle.) –

+0

@Matthew - nie przyłączasz się() do nowego wątku, prawda? To by to tłumaczyło ... –

+1

@Marc Gravell - z pamięci W pewnym momencie dołączyłem do wątku, ale nie jestem pewien, czy zachowanie było takie samo, kiedy tego nie używałem. Powodem przyłączenia się jest to, że chciałem zablokować resztę aplikacji, dopóki praca się nie zakończy, ale może będę mógł skorzystać z alternatywy. –

Odpowiedz

5

Mówisz, że tworzysz nowy wątek STA, czy to dyspozytor w tym nowym wątku działa?

Dostaję od "this.Dispatcher.Thread! = Thread.CurrentThread", że oczekujesz, że będzie to inny dispatcher. Upewnij się, że działa, w przeciwnym razie nie przetworzy kolejki.

+0

Keith, To jest dobra uwaga. Nie jestem dość obeznany z dyspozytorem, ale czy dyspozytor okienny już się nie rozpoczął? Wątek STA służy do utworzenia nowego okna, jednak jeśli muszę ręcznie uruchomić program rozsyłający, wyjaśnię, dlaczego nie jest on przetwarzany ... –

+0

Jeśli tworzysz STA, spróbuj wywołać funkcję Dispatcher.Run() po wyświetleniu okna . Rozumiem, że dyspozytor jest pompą komunikatów i jeśli tworzy nowy wątek UI, zostanie utworzony przez dyspozytora na żądanie, jeśli zarządzasz tworzeniem, będziesz musiał zadzwonić Uruchom na dyspozytorze. – Keith

+1

Spójrz na ten post: http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/ – Keith

10

Wywołanie jest synchroniczne - chcesz Dispatcher.BeginInvoke. Ponadto wierzę, że próbka Twojego kodu powinna przenieść "SetValue" wewnątrz instrukcji "else".

2

To brzmi jak zakleszczenie; Zdarza się to zazwyczaj, gdy wątek wywołujący .Invoke już posiadał blokadę/mutex/etc, której wątek UI musi ukończyć. Najprostszym sposobem byłoby użycie BeginInvoke zamiast tego: w ten sposób bieżący wątek może nadal działać i (prawdopodobnie) zwolni blokadę wkrótce - pozwalając UI na jej zdobycie. Ewentualnie, jeśli potrafisz zidentyfikować przestępczą blokadę, możesz ją celowo zwolnić na pewien czas.

+0

Dzięki Marc, To wyjaśnienie jest dobre, ale wciąż nie mam pojęcia, dlaczego jest blokada w pierwszej kolejności.Zgodnie z sugestią pana i Paula BeginInvoke była opcją, ale nie optymalną, nie ma żadnej gwarancji na jej zakończenie. Szalone dziwne błędy ... –

1

Mam podobny problem, a ja nadal nie jestem pewien, co odpowiedzią jest, myślę, że

if(this.Dispatcher.Thread != Thread.CurrentThread) 
{ 
    this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{ 
     this.Name = value; // Call same setter, but on the UI thread 
    }); 
    return; 
} 

należy zastąpić

if(this.Dispatcher.CheckAccess()) 
{ 
    this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{ 
     this.Name = value; // Call same setter, but on the UI thread 
    }); 
    return; 
} 

CheckAccess nie pokaże w Intellisense, ale jest tam i jest przeznaczony do tego celu. Ponadto, zgadzam się, że generalnie chcesz BeginInvoke tutaj, jednak odkryłem, że nie otrzymuję aktualizacji UI, gdy robię to asynchronicznie. Niestety, gdy robię to synchronicznie, otrzymuję stan zakleszczenia ...

3

Myślę, że masz na myśli, jeśli (! This.Dispatcher.CheckAccess())

Ja też czuło się powiesić z Invoke, czy mogę BeginInvoke mój pełnomocnik nie jest nazywany - zdają się robić wszystko, by książki :-(

0

wiem, że to jest stary wątek, ale tutaj jest kolejnym rozwiązaniem

właśnie stałe podobny problem mój dyspozytor został uruchomiony w porządku, więc ...

musiałem pokazać DEBUG -..> okno wątek zidentyfikować wszystkie wątki, które wykonują mój kod w dowolnym miejscu:

Według c przekrzykując każdy z wątków, szybko zorientowałem się, który wątek spowodował impas.

Było to wiele wątków łączących oświadczenie lock (locker) { ... } i wywołań do Dispatcher.Invoke().

W moim przypadku mogę po prostu zmienić konkretne oświadczenie lock (locker) { ... } i zastąpić je Interlocked.Increment(ref lockCounter).

To rozwiązało mój problem, ponieważ udało się uniknąć impasu.

void SynchronizedMethodExample() { 

    /* synchronize access to this method */ 
    if (Interlocked.Increment(ref _lockCounter) != 1) { return; } 

    try { 
    ... 
    } 
    finally { 
     _mandatoryCounter--; 
    } 
} 
7

Myślę, że to lepiej pokazać za pomocą kodu. Rozważmy następujący scenariusz:

nawlec to robi:

lock (someObject) 
{ 
    // Do one thing. 
    someDispatcher.Invoke(() => 
    { 
     // Do something else. 
    } 
} 

Wątek B to robi:

someDispatcher.Invoke(() => 
{ 
    lock (someObject) 
    { 
     // Do something. 
    } 
} 

Wszystko może pojawić cacy na pierwszy rzut oka, ale jej nie. To spowoduje impas. Dyspozytorzy są jak kolejki na wątek, a kiedy mamy do czynienia z zakleszczeniami takimi jak te, ważne jest, aby myśleć o nich w ten sposób: "Jaka poprzednia wysyłka mogła zablokować moją kolejkę?". Wątek A pojawi się ... i wyśle ​​pod zamek. Ale co, jeśli wątek B pojawi się w momencie, w którym wątek A jest w kodzie oznaczonym "Czy jedna rzecz"? No cóż ...

  • Wątek A ma blokadę someObject i uruchamia kod.
  • Wątek B teraz wywołuje, a dyspozytor spróbuje uzyskać blokadę elementu SomeObject, blokując dyspozytora, ponieważ wątek A ma już tę blokadę.
  • Wątek A spowoduje umieszczenie kolejnej pozycji wysyłki. Ten przedmiot nigdy nie zostanie wyrzucony, ponieważ twój dyspozytor nigdy nie zakończy przetwarzania poprzedniego żądania; jest już zablokowany.

Masz teraz piękny zakleszczenie.

+0

Dzięki za dobre wyjaśnienie. Zaoszczędziłeś mi godziny pracy. Naprawiłem to, nie uzyskując blokad w wywołaniach dyspozytora (co robi twój wątek B). Czy istnieje inne rozwiązanie tego problemu? – Heribert

+0

@Heribert To zależy od kodu, z którym pracujesz. Zakleszczenia takie jak te są bardzo specyficzne dla aplikacji. Jeśli masz do czynienia z przypadkiem podobnym do powyższego, możesz spróbować zablokować poza rozmowami dyspozytora. – Alexandru

+0

To właśnie zrobiłem :) Jeszcze raz dziękuję za wpis i odpowiedź – Heribert

Powiązane problemy