2013-04-17 9 views
137

Ogni post del blog che ho letto ti dice come consumare un metodo asincrono in C#, ma per qualche strana ragione non spiega mai come costruire i tuoi propri metodi asincroni per consumare. Così ho questo codice proprio ora che consuma il mio metodo:Come si crea un metodo asincrono in C#?

private async void button1_Click(object sender, EventArgs e) 
{ 
    var now = await CountToAsync(1000); 
    label1.Text = now.ToString(); 
} 

E ho scritto questo metodo che è CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000) 
{ 
    return Task.Factory.StartNew(() => 
    { 
     for (int i = 0; i < num; i++) 
     { 
      Console.WriteLine("#{0}", i); 
     } 
    }).ContinueWith(x => DateTime.Now); 
} 

È questo, l'uso di Task.Factory, il modo migliore per scrivere un metodo asincrono, o dovrei scrivere in un altro modo?

+1

Dal momento che non sappiamo cosa stai facendo, come dovremmo sapere qual è il modo migliore per farlo? Il modo in cui eseguirai un calcolo asincrono di un'immagine frattale è molto diverso rispetto al modo in cui eseguiresti un download asincrono di un file. –

+14

Sto facendo una domanda generale su come strutturare un metodo. Voglio solo sapere da dove iniziare a trasformare i miei metodi già sincroni in quelli asincroni. –

+2

OK, quindi cosa fa un tipico metodo sincrono * do *, e * perché vuoi renderlo asincrono *? –

risposta

172

Non è consigliabile StartNew a meno che non sia necessario tale livello di complessità.

Se il metodo asincrono dipende da altri metodi asincroni, l'approccio più semplice è quello di utilizzare la parola chiave async:

private static async Task<DateTime> CountToAsync(int num = 10) 
{ 
    for (int i = 0; i < num; i++) 
    { 
    await Task.Delay(TimeSpan.FromSeconds(1)); 
    } 

    return DateTime.Now; 
} 

Se il metodo asincrono sta facendo il lavoro della CPU, è necessario utilizzare Task.Run:

private static async Task<DateTime> CountToAsync(int num = 10) 
{ 
    await Task.Run(() => ...); 
    return DateTime.Now; 
} 

Potresti trovare utile il mio async/await intro.

+3

@Stephen: "Se il tuo metodo asincrono dipende da altri metodi asincroni" - ok, ma cosa succede se non è così. Cosa succede se si tenta di avvolgere un po 'di codice che chiama BeginInvoke con un codice di richiamata? – Ricibob

+0

@Ricibob: dovresti usare 'TaskFactory.FromAsync' per racchiudere' BeginInvoke'. Non sei sicuro di cosa intendi per "codice di richiamata"; sentiti libero di pubblicare la tua domanda con il codice. –

+0

@Stephen: Grazie - sì TaskFactory.FromAsync è quello che stavo cercando. – Ricibob

-8

Basta aggiungere un paio di parole per il codice - "asincrono" e "attendere":

private async Task<DateTime> CountToAsync(int num = 1000) 
{ 
    return await Task.Factory.StartNew(() => 
    { 
     for (int i = 0; i < num; i++) 
     { 
      Console.WriteLine("#{0}", i); 
     } 
    }).ContinueWith(x => DateTime.Now); 
} 
+3

L'uso di 'await' non ha aggiunto nulla al codice che non c'era prima, a differenza della risposta accettata che fa leva su' await' per semplificare il codice. La tua soluzione * aggiunge * la complessità, piuttosto che rimuoverla. – Servy

+1

Si prega di spiegare perché si dovrebbe aggiungere questo al codice. Non vedo alcun beneficio. – Florian

2

Se non si desidera utilizzare async/await all'interno del vostro metodo, ma ancora "decorare" è in modo tale da essere in grado di utilizzare la parola chiave attendere dall'esterno, TaskCompletionSource.cs:

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>   
    { 
     try 
     { 
      T result = function(); 
      tcs.SetResult(result); 
     } 
     catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
} 

From here e here

Per supportare tale paradigma con Task, abbiamo bisogno di un modo per mantenere la facciata del Task e la possibilità di fare riferimento a un'operazione arbitraria asincrona come un'attività, ma di controllare la durata di tale attività in base alle regole dell'infrastruttura sottostante che fornisce l'asincronia e farlo in un modo che non costa in modo significativo. Questo è lo scopo di TaskCompletionSource.

I saw è anche utilizzato nella fonte .NET es. WebClient.cs:

[HostProtection(ExternalThreading = true)] 
    [ComVisible(false)] 
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data) 
    { 
     // Create the task to be returned 
     var tcs = new TaskCompletionSource<string>(address); 

     // Setup the callback event handler 
     UploadStringCompletedEventHandler handler = null; 
     handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion); 
     this.UploadStringCompleted += handler; 

     // Start the async operation. 
     try { this.UploadStringAsync(address, method, data, tcs); } 
     catch 
     { 
      this.UploadStringCompleted -= handler; 
      throw; 
     } 

     // Return the task that represents the async operation 
     return tcs.Task; 
    } 

Infine, ho trovato utili anche i seguenti:

ho chiesto questa domanda tutto il tempo. L'implicazione è che ci deve essere qualche thread da qualche parte che sta bloccando la chiamata I/O alla risorsa esterna. Quindi, il codice asincrono libera il thread di richiesta, ma solo a spese di un altro thread altrove nel sistema, giusto? No, per niente. Per capire perché le richieste asincrone si ridimensionano, traccerò un esempio (semplificato) di una chiamata I/O asincrona. Diciamo che una richiesta deve scrivere su un file. Il thread di richiesta chiama il metodo di scrittura asincrono. WriteAsync è implementato dalla Base Class Library (BCL) e utilizza le porte di completamento per l'I/O asincrono. Quindi, la chiamata WriteAsync viene passata al sistema operativo come una scrittura di file asincrona. Il sistema operativo comunica quindi con lo stack del driver, passando i dati per scrivere in un pacchetto di richiesta I/O (IRP).È qui che le cose si fanno interessanti: se un driver di periferica non può gestire immediatamente un IRP, deve gestirlo in modo asincrono. Quindi, il driver dice al disco di iniziare a scrivere e restituisce una risposta "in sospeso" al sistema operativo. Il sistema operativo passa la risposta "in sospeso" al BCL e il BCL restituisce un'attività incompleta al codice di gestione delle richieste. Il codice di gestione richieste attende l'attività, che restituisce un'attività incompleta da tale metodo e così via. Infine, il codice di gestione richieste termina con la restituzione di un'attività incompleta in ASP.NET e il thread di richiesta viene liberato per tornare al pool di thread.

Introduction to Async/Await on ASP.NET

Se l'obiettivo è quello di migliorare la scalabilità (piuttosto che di risposta), il tutto si basa sull'esistenza di un I/O esterno che offre l'opportunità di farlo.

+0

Se verrà visualizzato il commento sopra [Implementazione Task.Run] (https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs.298c51c20d5a4fad) ad es. _Queue il lavoro specificato da eseguire su ThreadPool e restituisce un handle di attività per quel lavoro_. In realtà fai lo stesso. Sono sicuro che Task.Run sarà migliore, perché gestirà internamente CancelationToken e farà alcune ottimizzazioni con la pianificazione e le opzioni di esecuzione. – unsafePtr

Problemi correlati