web-dev-qa-db-de.com

Wie erstellen Sie eine asynchrone Methode in C #?

In jedem Blogbeitrag, den ich gelesen habe, erfahren Sie, wie Sie eine asynchrone Methode in C # verwenden. Erklären Sie jedoch aus irgendeinem Grund niemals, wie Sie Ihre eigenen asynchronen Methoden für die Verwendung erstellen. Ich habe jetzt diesen Code, der meine Methode verbraucht:

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

Und ich habe diese Methode geschrieben, die CountToAsync ist:

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

Ist dies die Verwendung von Task.Factory, der beste Weg, eine asynchrone Methode zu schreiben, oder sollte ich das anders schreiben?

180

Ich empfehle StartNew nur, wenn Sie diese Komplexität benötigen.

Wenn Ihre asynchrone Methode von anderen asynchronen Methoden abhängig ist, verwenden Sie am einfachsten das Schlüsselwort 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;
}

Wenn Ihre asynchrone Methode CPU-Arbeit leistet, sollten Sie Task.Run Verwenden:

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

Sie finden vielleicht mein async/await-Intro hilfreich.

213
Stephen Cleary

Wenn Sie async/await in Ihrer Methode nicht verwenden möchten, es aber dennoch "dekorieren" möchten, um das Schlüsselwort await von außen verwenden zu können, 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; 
}

von hier und hier

Um ein solches Paradigma mit Tasks zu unterstützen, benötigen wir eine Möglichkeit, die Task-Fassade und die Fähigkeit beizubehalten, einen beliebigen asynchronen Vorgang als Task zu bezeichnen, aber die Lebensdauer dieses Tasks gemäß den Regeln der zugrunde liegenden Infrastruktur zu steuern, die die bereitstellt Asynchronität, und das auf eine Weise, die nicht viel kostet. Dies ist der Zweck von TaskCompletionSource.

Ich sah, wird auch in der .NET-Quelle verwendet, z. 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;
    }

Zum Schluss fand ich auch folgendes sinnvoll:

Diese Frage wird mir die ganze Zeit gestellt. Die Implikation ist, dass irgendwo ein Thread vorhanden sein muss, der den E/A-Aufruf an die externe Ressource blockiert. Durch asynchronen Code wird also der Anforderungsthread freigegeben, jedoch nur auf Kosten eines anderen Threads an einer anderen Stelle im System. Nein überhaupt nicht. Um zu verstehen, warum asynchrone Anforderungen skaliert werden, verfolge ich ein (vereinfachtes) Beispiel für einen asynchronen E/A-Aufruf. Angenommen, eine Anfrage muss in eine Datei geschrieben werden. Der Anforderungsthread ruft die asynchrone Schreibmethode auf. WriteAsync wird von der Base Class Library (BCL) implementiert und verwendet Abschlussports für die asynchrone E/A. Daher wird der WriteAsync-Aufruf als asynchroner Dateischreibvorgang an das Betriebssystem übergeben. Das Betriebssystem kommuniziert dann mit dem Treiberstapel und leitet die Daten zum Schreiben in ein E/A-Anforderungspaket (IRP) weiter. Hier wird es interessant: Wenn ein Gerätetreiber ein IRP nicht sofort verarbeiten kann, muss er es asynchron verarbeiten. Der Treiber fordert die Festplatte auf, mit dem Schreiben zu beginnen, und gibt eine ausstehende Antwort an das Betriebssystem zurück. Das Betriebssystem übergibt diese "ausstehende" Antwort an die BCL, und die BCL gibt eine unvollständige Aufgabe an den Anforderungsbearbeitungscode zurück. Der Anforderungsbearbeitungscode wartet auf die Aufgabe, die eine unvollständige Aufgabe von dieser Methode usw. zurückgibt. Schließlich gibt der Anforderungsbehandlungscode eine unvollständige Aufgabe an ASP.NET zurück, und der Anforderungsthread kann wieder in den Threadpool zurückkehren.

Einführung in Async/Await unter ASP.NET

Wenn das Ziel darin besteht, die Skalierbarkeit zu verbessern (anstatt die Reaktionsfähigkeit zu verbessern), hängt dies alles von der Existenz einer externen E/A ab, die die Möglichkeit dazu bietet.

11
Alberto