2008-11-05 8 views
21

Ho un'applicazione WPF piuttosto complessa che sembra "sospesa" o si blocca in una chiamata di attesa quando si tenta di utilizzare il dispatcher per richiamare una chiamata sul thread dell'interfaccia utente.WPF Dispatcher.Invoke 'sospeso'

Il processo generale è:

  1. gestire l'evento clic su un pulsante
  2. creare un nuovo thread (STA), che: crea una nuova istanza del presentatore e interfaccia utente, quindi chiama il Disconnect metodo
  3. Disconnect imposta poi una proprietà sulla UI chiamato Nome
  4. il setter per nome, allora utilizza il seguente codice per impostare la proprietà:

    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. 

Il mio problema è che quando il dispatcher invocare metodo viene chiamato sembra bloccarsi ogni volta, e lo stack di chiamate indica che il suo in un sonno, attendere o partecipare nell'ambito dell'attuazione Invoke.

Quindi, c'è qualcosa che sto facendo male che mi manca, ovvio o no, o c'è un modo migliore di chiamare attraverso il thread dell'interfaccia utente per impostare questa proprietà (e altri)?

Edit: La soluzione fu di chiamare System.Windows.Threading.Dispatcher.Run() alla fine del delegato filo (ad esempio dove veniva eseguito il lavoro) - Grazie a tutti coloro che hanno contribuito.

+0

@Matthew: in realtà, non c'è nulla di "non ottimale" su BeginInvoke; se non hai assolutamente bisogno di un aggiornamento * ora *, va bene. È necessario essere un po 'cauti sulle variabili acquisite, anche se (non modificare il "valore" dopo aver chiamato BeginInvoke.) –

+0

@Matthew - you do not Join() il nuovo thread, vero? Questo lo spiegherebbe ... –

+1

@Marc Gravell - a memoria, stavo unendo il filo ad un certo punto, ma non sono sicuro che il comportamento fosse lo stesso quando non lo stavo usando. Il motivo del join è che volevo bloccare il resto dell'app fino al completamento del lavoro, ma forse posso usare un'alternativa. –

risposta

5

Si dice che si sta creando un nuovo thread STA, è in esecuzione il dispatcher su questo nuovo thread?

Sto ottenendo da "this.Dispatcher.Thread! = Thread.CurrentThread" che ci si aspetta che sia un dispatcher diverso. Assicurati che sia in esecuzione altrimenti non elaborerà la coda.

+0

Keith, questo è un buon punto. Non ho familiarità con il dispatcher, ma non sarebbe già stato avviato il dispatcher della finestra? Il thread STA viene utilizzato per creare la nuova finestra, tuttavia, se ho bisogno di avviare manualmente il dispatcher, spiegherebbe perché non sta elaborando ... –

+0

Se crei lo STA da solo prova a chiamare Dispatcher.Run() dopo aver mostrato la tua finestra . La mia comprensione è che il dispatcher è un message pump e, se si crea un nuovo thread dell'interfaccia utente, verrà creato un dispatcher quando richiesto, se si gestisce la creazione è necessario chiamare Esegui sul dispatcher. – Keith

+1

Dai un'occhiata a questo post: http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/ – Keith

10

Invoke è sincrono - si desidera Dispatcher.BeginInvoke. Inoltre, credo che il tuo esempio di codice dovrebbe spostare il "SetValue" all'interno di un'istruzione "else".

2

Questo sembra un punto morto; questo normalmente si verifica se il thread chiama .Invoke contiene già un lock/mutex/etc che il thread UI deve completare per funzionare. L'approccio più semplice sarebbe utilizzare BeginInvoke: in questo modo, il thread corrente può continuare a funzionare e (presumibilmente) rilasciare il blocco a breve, consentendo all'interfaccia utente di acquisirlo. In alternativa, se è possibile identificare il blocco offendente, è possibile rilasciarlo deliberatamente per una durata.

+0

Grazie Marc, questa spiegazione è buona, tuttavia sono ancora senza tracce sul motivo per cui c'è una serratura in primo luogo.Come suggerito da te stesso e Paul BeginInvoke era un'opzione ma non ottimale, non vi è alcuna garanzia che si completi. Pazzi bug strani .... –

1

Sto avendo un problema simile e mentre io non sono ancora sicuro di quale sia la risposta, penso che la tua

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; 
} 

dovrebbe essere sostituito da

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

CheckAccess non mostrerà in Intellisense ma è lì e destinato a questo scopo. Inoltre, sono d'accordo sul fatto che in generale tu voglia BeginInvoke qui, tuttavia ho scoperto che non ottengo aggiornamenti dell'interfaccia utente quando eseguo questo async. Sfortunatamente, quando lo faccio in modo sincrono ottengo una condizione di deadlock ...

3

Penso che intendi se (! Questo.Dispatcher.CheckAccess())

sto anche geting un appendere con Invoke, o se posso BeginInvoke mio delegato non viene chiamato - sembrano fare tutto dal libro :-(

0

So che questo è un thread vecchio, ma qui è un'altra soluzione

ho appena risolto un problema simile mio dispatcher stava funzionando bene, quindi ...

ho dovuto mostrare il DEBUG -..> fILO FINESTRA per identificare tutti i fili che sto eseguendo il mio codice ovunque.

c controllando ciascuno dei thread, ho visto rapidamente quale thread ha causato il deadlock.

Si trattava di più thread combinando un'istruzione lock (locker) { ... } e chiamate a Dispatcher.Invoke().

Nel mio caso, ho potuto semplicemente modificare una specifica istruzione lock (locker) { ... } e sostituirla con Interlocked.Increment(ref lockCounter).

Questo ha risolto il problema perché il deadlock è stato evitato.

void SynchronizedMethodExample() { 

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

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

Penso che questo sia meglio mostrato con il codice. Consideriamo questo scenario:

Discussione A fa questo:

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

Discussione B fa questo:

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

Tutto potrebbe apparire bene e dandy, a prima vista, ma la sua non. Questo produrrà un deadlock. I dispatcher sono come le code di un thread e, quando si tratta di deadlock come questi, è importante pensarli in questo modo: "Quale dispatch precedente avrebbe potuto bloccare la mia coda?". Il thread A entrerà in ... e verrà inviato sotto una serratura. Ma, cosa succede se il thread B arriva nel momento in cui il thread A è nel codice contrassegnato come "Fai una cosa"? Bene ...

  • Il thread A ha il blocco su someObject e sta eseguendo del codice.
  • Il thread B ora invia e il dispatcher tenterà di ottenere il blocco su someObject, bloccando il dispatcher poiché il thread A ha già quel blocco.
  • Il thread A quindi accoderà un altro elemento di spedizione. Questo oggetto non verrà mai sparato, perché il tuo committente non finirà mai di elaborare la tua richiesta precedente; è già inceppato.

Ora avete una bella situazione di stallo.

+0

Grazie per la buona spiegazione. Mi ha risparmiato ore di lavoro. L'ho risolto non acquisendo i blocchi nelle chiamate del dispatcher (quale è il tuo thread B). C'è un'altra soluzione per questo problema? – Heribert

+0

@Heribert Dipende dal codice con cui stai lavorando. Deadlock come questi sono molto specifici per le applicazioni. Se si sta affrontando un caso simile a quanto sopra, è possibile provare a bloccare fuori dalle chiamate del dispatcher. – Alexandru

+0

è quello che ho fatto :) Grazie ancora per il post e risposta – Heribert