Continuazione dell'attività sul thread dell'interfaccia utente


214

Esiste un modo "standard" per specificare che una continuazione dell'attività deve essere eseguita sul thread da cui è stata creata l'attività iniziale?

Attualmente ho il codice qui sotto: funziona, ma tenere traccia del dispatcher e creare una seconda azione sembra un sovraccarico inutile.

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

Nel caso del tuo esempio, potresti usare Control.Invoke(Action), ad es. TextBlock1.Invokepiuttosto chedispatcher.Invoke
Colonnello Panic

2
Grazie @ColonelPanic, ma stavo usando WPF (come taggato), non winforms.
Greg Sansom,

Risposte:


352

Chiama il seguito con TaskScheduler.FromCurrentSynchronizationContext():

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

Questo è adatto solo se il contesto di esecuzione corrente si trova sul thread dell'interfaccia utente.


40
È valido solo se il contesto di esecuzione corrente si trova nel thread dell'interfaccia utente. Se inserisci questo codice in un'altra attività, ricevi InvalidOperationException (guarda la sezione Eccezioni )
stukselbax,

3
In .NET 4.5 la risposta di Johan Larsson dovrebbe essere usata come modo standard per una continuazione dell'attività sul thread dell'interfaccia utente. Basta scrivere: waitit Task.Run (DoLongRunningWork); this.TextBlock1.Text = "Complete"; Vedi anche: blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Marcel W,

1
Grazie per avermi salvato la vita. Trascorro ore per capire come chiamare le cose principali del thread all'interno di wait / ContinueWith. Per tutti gli altri come sta usando Google Firebase SDK per Unity e ha ancora gli stessi problemi, questo è un approccio funzionante.
CAP

2
@MarcelW - awaitè un buon modello - ma solo se ti trovi in ​​un asynccontesto (come un metodo dichiarato async). In caso contrario, è ancora necessario fare qualcosa di simile a questa risposta.
ToolmakerSteve

33

Con async fai semplicemente:

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);

Però:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

2
Il commento sotto la falseversione mi confonde. Ho pensato falseche potrebbe continuare su un thread diverso .
ToolmakerSteve

1
@ToolmakerSteve Dipende da quale thread stai pensando. Il thread di lavoro utilizzato da Task.Run o il thread del chiamante? Ricorda, "stesso thread su cui è terminata l'attività" indica il thread di lavoro (evitando di passare da un thread all'altro). Inoltre, ConfigureAwait (true) non garantisce che il controllo ritorni allo stesso thread , solo allo stesso contesto (sebbene la distinzione potrebbe non essere significativa).
Max Barraclough,

@MaxBarraclough - Grazie, ho letto male quale "stesso thread" intendesse. evitare di passare da un thread all'altro nel senso di massimizzare le prestazioni utilizzando qualsiasi thread che si sta eseguendo [per eseguire l'attività "fare alcune cose"], questo mi chiarisce.
ToolmakerSteve

1
La domanda non specifica di essere all'interno di un asyncmetodo (che è necessario, da usare await). Qual è la risposta quando awaitnon è disponibile?
ToolmakerSteve

22

Se hai un valore di ritorno che devi inviare all'interfaccia utente, puoi utilizzare la versione generica in questo modo:

Nel mio caso questo viene chiamato da un ViewModel MVVM.

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());

Sto indovinando il = prima di GenerateManifest è un errore di battitura.
Sebastien F.

Sì - Andato adesso! Grazie.
Simon_Weaver,

11

Volevo solo aggiungere questa versione perché è un thread così utile e penso che sia un'implementazione molto semplice. L'ho usato più volte in vari tipi se l'applicazione multithread:

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

2
Non effettuare il downvoting poiché questa è una soluzione praticabile in alcuni scenari; tuttavia, la risposta accettata è decisamente migliore. È indipendente dalla tecnologia ( TaskSchedulerfa parte di BCL, Dispatchernon lo è) e può essere utilizzato per comporre catene complesse di attività a causa del fatto di non doversi preoccupare di eventuali operazioni asincrone di incendio (come BeginInvoke).
Kirill Shlenskiy il

@Kirill puoi espandere un po ', perché alcuni thread SO hanno dichiarato all'unanimità che il dispatcher è il metodo corretto se si utilizza WPF di WinForms: si può invocare un aggiornamento della GUI in modo asincrono (usando BeginInvoke) o in modo sincrono (Invoke), sebbene tipicamente asincrono viene utilizzato perché non si vorrebbe bloccare un thread in background solo per un aggiornamento della GUI. FromCurrentSynchronizationContext non inserisce l'attività di continuazione nella coda dei messaggi del thread principale esattamente come il dispatcher?
Decano del

1
Giusto, ma l'OP sta sicuramente chiedendo di WPF (e lo ha taggato così), e non vuole mantenere un riferimento a nessun dispatcher (e presumo sia anche un contesto di sincronizzazione - puoi ottenerlo solo dal thread principale e devi memorizzare un riferimento ad esso da qualche parte). Ecco perché mi piace la soluzione che ho pubblicato: c'è un riferimento statico thread-safe incorporato che non richiede nulla di tutto ciò. Penso che questo sia estremamente utile nel contesto del WPF.
Decano del

3
Volevo solo rafforzare il mio ultimo commento: lo sviluppatore non deve solo archiviare il contesto di sincronizzazione, ma deve sapere che questo è disponibile solo dal thread principale; questo problema è stato causa di confusione in dozzine di domande SO: Le persone cercano continuamente di ottenerlo dal thread di lavoro. Se il loro codice è stato spostato in un thread di lavoro, non riesce a causa di questo problema. Quindi, a causa della prevalenza del WPF, questo dovrebbe sicuramente essere chiarito qui in questa domanda popolare.
Decano del

1
... tuttavia, l'osservazione di Dean sulla [risposta accettata] che deve tenere traccia del contesto di sincronizzazione se il codice potrebbe non essere sul thread principale è importante da notare, ed evitare che sia un vantaggio di questa risposta.
ToolmakerSteve

1

Sono arrivato qui tramite Google perché stavo cercando un buon modo per fare cose sul thread awaitdell'interfaccia utente dopo essere stato all'interno di una chiamata Task.Run: utilizzando il seguente codice è possibile utilizzare per tornare nuovamente al thread dell'interfaccia utente.

Spero che questo aiuti qualcuno.

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}

Uso:

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

1
Molto intelligente! Abbastanza poco intuitivo però. Suggerisco di fare staticla lezione UI.
Theodor Zoulias,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.