Devi mettere Task.Run in un metodo per renderlo asincrono?


304

Sto cercando di capire che l'attesa asincrona nella forma più semplice. Voglio creare un metodo molto semplice che aggiunge due numeri per il bene di questo esempio, garantito, non è affatto tempo di elaborazione, è solo una questione di formulare un esempio qui.

Esempio 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

Esempio 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

Se aspetto DoWork1Async()il codice verrà eseguito in modo sincrono o asincrono?

Devo racchiudere il codice di sincronizzazione Task.Runper rendere il metodo attendibile E asincrono in modo da non bloccare il thread dell'interfaccia utente?

Sto cercando di capire se il mio metodo è un Tasko restituisce Task<T>devo avvolgere il codice Task.Runper renderlo asincrono.

Domanda stupida, ne sono sicuro, ma vedo esempi in rete in cui le persone sono in attesa di codice che non ha nulla di asincrono e non racchiuso in un Task.Runo StartNew.


30
Il tuo primo frammento non ti dà avvertimenti?
svick

Risposte:


587

Innanzitutto, chiariamo alcuni termini: "asincrono" ( async) significa che potrebbe restituire il controllo al thread chiamante prima che inizi. In un asyncmetodo, quei punti di "rendimento" sonoawait espressioni.

Questo è molto diverso dal termine "asincrono", poiché (mis) usato da anni dalla documentazione MSDN per significare "esegue su un thread in background".

Per confondere ulteriormente il problema, asyncè molto diverso da "aspettabile"; ci sono alcuni asyncmetodi i cui tipi di restituzione non sono attendibili e molti metodi che restituiscono tipi attendibili che non lo sono async.

Basta con quello che non sono ; ecco cosa sono :

  • La asyncparola chiave consente un metodo asincrono (ovvero consente awaitespressioni). asynci metodi possono restituire Task, Task<T>oppure (se necessario) void.
  • Qualsiasi tipo che segue un certo modello può essere attendibile. I tipi più comuni attesi sono Taske Task<T>.

Quindi, se riformuliamo la tua domanda su "come posso eseguire un'operazione su un thread in background in modo che sia attendibile", la risposta è usare Task.Run:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(Ma questo modello è un approccio scadente; vedi sotto).

Ma se la tua domanda è "come posso creare un asyncmetodo che può restituire al suo chiamante invece di bloccare", la risposta è dichiarare il metodo asynce usare awaitper i suoi punti "cedenti":

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

Quindi, lo schema di base delle cose è che il asynccodice dipenda da "attendibili" nelle sue awaitespressioni. Questi "attendibili" possono essere altri asyncmetodi o solo metodi regolari che restituiscono gli aspetti attendibili. Metodi regolari ritorno Task/ Task<T> possono utilizzare Task.Runper eseguire codice su un thread in background, o (più comunemente) che possono utilizzare TaskCompletionSource<T>o uno dei suoi tasti di scelta rapida ( TaskFactory.FromAsync, Task.FromResult, ecc). Io non consiglio avvolgendo un intero metodo Task.Run; i metodi sincroni dovrebbero avere firme sincrone e dovrebbe essere lasciato al consumatore se debba essere racchiuso in un Task.Run:

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

Ho async/ awaitintro sul mio blog; alla fine ci sono alcune buone risorse di follow-up. Anche i documenti MSDN per asyncsono insolitamente buoni.


8
@sgnsajgon: Sì. asyncmetodi devono tornare Task, Task<T>o void. Taske Task<T>sono attendibili; voidnon è.
Stephen Cleary,

3
In realtà, async voidverrà compilata una firma del metodo, è un'idea davvero terribile quando
perdi

4
@TopinFrassi: Sì, verranno compilati, ma voidnon è attendibile.
Stephen Cleary,

4
@ohadinho: No, quello di cui sto parlando nel post del blog è quando l'intero metodo è solo una chiamata a Task.Run(come DoWorkAsyncin questa risposta). L'uso Task.Rundi chiamare un metodo da un contesto UI è appropriato (come DoVariousThingsFromTheUIThreadAsync).
Stephen Cleary,

2
Si, esattamente. È valido da usare Task.Runper invocare un metodo, ma se c'è un Task.Runtutto (o quasi) tutto il codice del metodo, allora si tratta di un anti-pattern - basta mantenere quel metodo sincrono e passare al Task.Runlivello superiore.
Stephen Cleary,

22

Una delle cose più importanti da ricordare quando si decora un metodo con asincrono è che almeno un operatore attende all'interno del metodo. Nel tuo esempio, lo tradurrei come mostrato sotto usando TaskCompletionSource .

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

26
Perché usi TaskCompletionSource, invece di restituire semplicemente l'attività restituita dal metodo Task.Run () (e cambiando il suo corpo per restituire il risultato)?
ironico

4
Solo una nota a margine. Un metodo che ha una firma "async void" è generalmente una cattiva pratica e considerato un codice errato in quanto può portare a un deadlock dell'interfaccia utente abbastanza facilmente. L'eccezione principale sono i gestori di eventi asincroni.
Jazzeroki,

12

Quando si utilizza Task.Run per eseguire un metodo, Task ottiene un thread dal threadpool per eseguire quel metodo. Quindi, dal punto di vista del thread dell'interfaccia utente, è "asincrono" in quanto non blocca il thread dell'interfaccia utente. Questo va bene per l'applicazione desktop poiché di solito non sono necessari molti thread per gestire le interazioni dell'utente.

Tuttavia, per l'applicazione Web ogni richiesta è gestita da un thread del pool di thread e quindi il numero di richieste attive può essere aumentato salvando tali thread. L'uso frequente di thread pool di thread per simulare il funzionamento asincrono non è scalabile per le applicazioni Web.

True Async non implica necessariamente l'utilizzo di un thread per operazioni di I / O, come l'accesso a file / DB, ecc. Puoi leggere questo per capire perché l'operazione di I / O non ha bisogno di thread. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

Nel tuo semplice esempio, è un puro calcolo associato alla CPU, quindi usare Task.Run va bene.


Quindi, se devo consumare un'API esterna sincrona all'interno di un controller API Web, NON dovrei racchiudere la chiamata sincrona in Task.Run ()? Come hai detto, in questo modo manterrai sbloccato il thread della richiesta iniziale ma sta utilizzando un altro thread del pool per chiamare l'API esterno. In effetti penso che sia ancora una buona idea perché in questo modo in teoria può usare due thread di pool per elaborare molte richieste, ad esempio un thread può elaborare molte richieste in arrivo e un altro può chiamare l'API esterno per tutte queste richieste?
stt106

Sono d'accordo. Non sto dicendo che non dovresti assolutamente avvolgere tutte le chiamate sincrone in Task.Run (). Sto semplicemente sottolineando un potenziale problema.
zheng yu,

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()è corretto. Se lo fai, cambieresti thread. cioè stai sbloccando il thread della richiesta iniziale ma stai prendendo un altro thread dal threadpool che avrebbe potuto essere usato per elaborare un'altra richiesta. L'unico risultato è un overhead di cambio di contesto quando la chiamata è completata per un guadagno assolutamente zero
Saeb Amini
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.