2012-11-01 9 views
5

Ho un Task che sto avviando e desidero attendere il completamento in un'app WPF. All'interno di questa attività invoco un Action sul dispatcher.Task.Wait vs Task.RunSyncronicamente dove l'attività ha chiamato a WPF Dispatcher.Invoke

Se utilizzo Task.Wait() sembra sospeso come se il metodo non fosse mai terminato. Inoltre, i punti di interruzione all'interno di Dispatcher.Invoke non vengono mai colpiti.

Se utilizzo Task.RunSyncronously(), sembra funzionare correttamente e i punti di interruzione all'interno del Dispatcher vengono colpiti.

Perché c'è una differenza?

Codice di esempio:

public void ExampleMethod() 
{ 
    // When doing the following: 
    var task = new Task(LoadStuff); 

    // This never returns: 
    task.Start(); 
    task.Wait(); 

    // This version, however, does: 
    task.RunSyncronously(); 
} 

private void LoadStuff() 
{ 
    ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true); 

    DispatchHelper.RunOnDispatcher(() => 
    { 
     ... 
    }); 
} 

public static class DispatchHelper 
{ 
    public static void RunOnDispatcher(Action action) 
    { 
     Application.Current.Dispatcher.Invoke(action); 
    } 
}  

risposta

5

Sì, c'è una grande differenza. Se si utilizza RunSyncronously, è sufficiente eseguire l'attività nel thread dell'interfaccia utente. Se lo si avvia in un thread in background e noi Wait, il codice è in esecuzione in un thread in background e il thread dell'interfaccia utente è bloccato. Se il codice all'interno di tale attività sta richiamando il thread dell'interfaccia utente e il thread dell'interfaccia utente viene bloccato (dallo Wait), è stato creato un deadlock e l'applicazione rimarrà bloccata.

Si noti che se si utilizzava, RunSyncronously su quell'attività da un thread non dell'interfaccia utente e il thread dell'interfaccia utente veniva bloccato da qualcos'altro, si vedrebbe comunque il deadlock.

Ora, per quanto riguarda ciò che si dovrebbe fare, ci sono davvero due opzioni qui:

  1. Il compito in sé in realtà non richiedere molto tempo, e in realtà dovrebbe essere eseguito in thread UI, piuttosto che in un thread in background. Il thread dell'interfaccia utente non verrà congelato (temporaneamente) per un tempo sufficientemente lungo da costituire un problema durante l'esecuzione di tutto questo direttamente nell'interfaccia utente. Se questo è il caso, probabilmente non dovresti nemmeno renderlo un compito, basta mettere il codice in un metodo e chiamare il metodo.

  2. L'attività richiede molto tempo per essere eseguita, quindi aggiorna l'interfaccia utente dopo aver eseguito tale operazione. Se questo è il caso, è importante che non sia RunSyncronously ma avviato in un thread in background. Per evitare che l'intera applicazione venga bloccata in un deadlock, significa che è necessario non bloccare il thread dell'interfaccia utente tramite una chiamata Wait. Quello che devi fare se hai del codice che vuoi eseguire dopo il completamento dell'attività, è aggiungere una continuazione all'attività. In C# 4.0 è possibile eseguire questa operazione chiamando lo ContinueWith sull'attività e aggiungendo un delegato da eseguire. In C# 5.0+ potresti invece await nell'attività pertinente (piuttosto che in Wait, che è in realtà una grande differenza) e automaticamente il resto del metodo verrà eseguito come una continuazione per te (in effetti è zucchero sintattico per una chiamata esplicita ContinueWith, ma è molto utile).

+0

Non intendi .NET 4.5, non esiste ancora .NET 5. Inoltre, i metodi asincroni generano una macchina a stati piuttosto che l'uso di ContinueWith. –

+0

@BrianReichle Intendevo C#, non .NET, ma sì, era sbagliato. Per quanto riguarda 'async', sì genera una macchina a stati, e quella macchina a stati si aggiunge come una continuazione del compito atteso. La macchina a stati è il meccanismo con cui sa come riprendere da dove è stato interrotto quando viene eseguita la continuazione, ma l'aggiunta di una continuazione all'attività attesa è come viene eseguito * qualsiasi cosa * quando termina l'operazione. – Servy

+0

La macchina di stato ripianifica tramite il server di attesa restituito da 'GetAwaiter()', non 'ContinueWith' (questo ci permette di attendere cose diverse dalle attività). Entrambi richiamano un'implementazione comune, ma 'task.GetAwaiter(). OnCompleted (...)' non chiama 'ContinueWith' (in particolare, il primo evita di creare una nuova attività). Ma suppongo che questo stia diventando pedante, la maggior parte delle persone non si preoccuperà dei dettagli dell'implementazione :) –

Problemi correlati