Freigeben über


Asynchrone Ausdrücke

In diesem Artikel wird die Unterstützung in F# für asynchrone Ausdrücke beschrieben. Asynchrone Ausdrücke bieten eine Möglichkeit zum asynchronen Ausführen von Berechnungen, d. h., ohne die Ausführung anderer Arbeiten zu blockieren. Beispielsweise können asynchrone Berechnungen verwendet werden, um Apps zu schreiben, die über UIs verfügen, die für Benutzer reaktionsfähig bleiben, während die Anwendung andere Aufgaben ausführt. Mit dem F#-Programmiermodell für asynchrone Workflows können Sie funktionale Programme schreiben und dabei die Details des Threadübergangs innerhalb einer Bibliothek ausblenden.

Asynchroner Code kann auch mithilfe von Taskausdrücken erstellt werden. So entstehen .NET-Aufgaben direkt. Die Verwendung von Aufgabenausdrücken wird bevorzugt, wenn Sie umfassend mit .NET-Bibliotheken zusammenarbeiten, die .NET-Aufgaben erstellen oder nutzen. Wenn Sie den meisten asynchronen Code in F# schreiben, werden asynchrone Ausdrücke von F# bevorzugt. Sie sind prägnanter, festgelegter und bestimmte Einschränkungen im Zusammenhang mit .NET-Aufgaben werden vermieden.

Syntax

async { expression }

Hinweise

In der vorherigen Syntax ist die durch expression dargestellte Berechnung so eingerichtet, dass sie asynchron ausgeführt wird, d. h. ohne den aktuellen Berechnungsthread zu blockieren, wenn asynchrone Standbyvorgänge, E/A und andere asynchrone Vorgänge durchgeführt werden. Asynchrone Berechnungen werden häufig in einem Hintergrundthread gestartet, während die Ausführung im aktuellen Thread fortgesetzt wird. Als Ausdruckstyp wird Async<'T> verwendet, wobei 'T der Typ ist, der vom Ausdruck zurückgegeben wird, wenn das Schlüsselwort return verwendet wird.

Die Async Klasse stellt Methoden bereit, die mehrere Szenarien unterstützen. Der allgemeine Ansatz besteht darin, Async Objekte zu erstellen, die die Berechnung oder Berechnung darstellen, die Sie asynchron ausführen möchten, und diese Berechnungen dann mithilfe einer der auslösenden Funktionen zu starten. Die von Ihnen verwendeten Auslöser hängen davon ab, ob Sie den aktuellen Thread, einen Hintergrundthread oder ein .NET-Task-Objekt verwenden möchten. Um beispielsweise eine asynchrone Berechnung im aktuellen Thread zu starten, können Sie Async.StartImmediateverwenden. Wenn Sie eine asynchrone Berechnung aus dem UI-Thread starten, blockieren Sie nicht die Hauptereignisschleife, die Benutzeraktionen wie Tastaturanschläge und Mausaktivitäten verarbeitet, sodass Ihre Anwendung reaktionsfähig bleibt.

Asynchrone Bindung mithilfe von let!

In einem asynchronen Ausdruck sind einige Ausdrücke und Vorgänge synchron, und einige sind asynchron. Wenn Sie eine Methode asynchron aufrufen, statt einer normalen let Bindung, verwenden Sie let!. Der Effekt von let! besteht darin, die Ausführung für andere Berechnungen oder Threads zu ermöglichen, während die Berechnung ausgeführt wird. Nachdem die rechte Seite der let!-Bindung zurückgegeben wurde, wird die Ausführung für den Rest des asynchronen Ausdrucks fortgesetzt.

Der folgende Code zeigt den Unterschied zwischen let und let!. Eine Codezeile, die let verwendet, erstellt einfach eine asynchrone Berechnung als Objekt, das Sie später mithilfe von zum Beispiel Async.StartImmediate oder Async.RunSynchronouslyausführen können. Die Codezeile, die let! verwendet, startet die Berechnung und führt eine asynchrone Wartezeit aus: Der Thread wird angehalten, bis das Ergebnis verfügbar ist, an dem die Ausführung fortgesetzt wird.

// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[])  = stream.AsyncRead(bufferSize)

let! kann nur verwendet werden, um direkt auf asynchrone F#-Berechnungsergebnisse zu wartenAsync<T>. Sie können indirekt auf andere Arten asynchroner Vorgänge warten:

  • .NET-Aufgaben, Task<TResult> und die nicht generische Task, durch Kombination mit Async.AwaitTask
  • .NET-Wertaufgaben, ValueTask<TResult> und die nicht generische ValueTask, durch Kombination mit .AsTask() und Async.AwaitTask
  • Alle Objekte, die dem in F# RFC FS-1097 angegebenen "GetAwaiter"-Muster entsprechen, in Kombination mit task { return! expr } |> Async.AwaitTask

Ablaufsteuerung

Asynchrone Ausdrücke können Ablaufsteuerungskonstrukte wie for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. else, und if .. then .. enthalten. Diese können wiederum weitere asynchrone Konstrukte mit Ausnahme der Handler with und finally enthalten, die synchron ausgeführt werden.

Asynchrone F#-Ausdrücke unterstützen das asynchrone try .. finally ..-Konstrukt nicht. Für diesen Fall können Sie einen Aufgabenausdruck verwenden.

use- und use!-Bindungen

Innerhalb von asynchronen Ausdrücken können use-Bindungen an Werte vom Typ IDisposable gebunden werden. Für letzteres wird der Bereinigungsvorgang asynchron ausgeführt.

Zusätzlich zu let!können Sie use! verwenden, um asynchrone Bindungen auszuführen. Der Unterschied zwischen let! und use! ist identisch mit dem Unterschied zwischen let und use. Bei use! wird das Objekt beim Schließen des aktuellen Bereichs verworfen. Beachten Sie, dass in der aktuellen Version von F# use! nicht zulässt, dass ein Wert auf null initialisiert wird, auch wenn use es erlaubt.

Asynchrone Grundtypen

Eine Methode, die eine einzelne asynchrone Aufgabe ausführt und das Ergebnis zurückgibt, wird als asynchroner Grundtypbezeichnet, und diese sind speziell für die Verwendung mit let!ausgelegt. Mehrere asynchrone Grundtypen werden in der F#-Kernbibliothek definiert. Zwei solche Methoden für Webanwendungen werden im Modul FSharp.Control.WebExtensionsdefiniert: WebRequest.AsyncGetResponse und HttpClient.GetStringAsync (umschlossen mit Async.AwaitTask aus Kompatibilität mit dem asynchronen Modell von F#). Beide Grundtypen laden Daten von einer Webseite mit einer URL herunter. AsyncGetResponse erzeugt ein System.Net.WebResponse-Objekt, und GetStringAsync erzeugt eine Zeichenfolge, die den HTML-Code für eine Webseite darstellt.

Im modul FSharp.Control.CommonExtensions sind mehrere Grundtypen für asynchrone E/A-Vorgänge enthalten. Diese Erweiterungsmethoden der System.IO.Stream Klasse sind Stream.AsyncRead und Stream.AsyncWrite.

Sie können auch eigene asynchrone Grundtypen schreiben, indem Sie eine Funktion oder Methode definieren, deren Text ein asynchroner Ausdruck ist.

Um asynchrone Methoden in .NET Framework zu verwenden, die für andere asynchrone Modelle mit dem asynchronen F#-Programmiermodell entwickelt wurden, erstellen Sie eine Funktion, die ein F#-Async-Objekt zurückgibt. Die F#-Bibliothek verfügt über Funktionen, die dies vereinfachen.

Ein Beispiel für die Verwendung asynchroner Ausdrücke wird hier aufgeführt; es gibt viele weitere Beispiele in der Dokumentation für die Methoden der Async-Klasse.

In diesem Beispiel wird gezeigt, wie Asynchrone Ausdrücke verwendet werden, um Code parallel auszuführen.

Im folgenden Codebeispiel ruft eine Funktion fetchAsync den von einer Webanforderung zurückgegebenen HTML-Text ab. Die fetchAsync-Funktion enthält einen asynchronen Codeblock. Wenn eine Bindung an das Ergebnis eines asynchronen Grundtyps vorgenommen wird, wird in diesem Fall an AsyncDownloadString, wird let! anstelle von let verwendet.

Sie verwenden die Funktion Async.RunSynchronously, um einen asynchronen Vorgang auszuführen und auf das Ergebnis zu warten. Als Beispiel können Sie mehrere asynchrone Vorgänge parallel ausführen, indem Sie die Async.Parallel-Funktion zusammen mit der Async.RunSynchronously-Funktion verwenden. Die Async.Parallel-Funktion verwendet eine Liste der Async Objekte, richtet den Code für jedes Async Aufgabenobjekt ein, das parallel ausgeführt werden soll, und gibt ein Async-Objekt zurück, das die parallele Berechnung darstellt. Genau wie bei einem einzelnen Vorgang rufen Sie Async.RunSynchronously auf, um die Ausführung zu starten.

Die runAll-Funktion startet drei asynchrone Ausdrücke parallel und wartet, bis alle abgeschlossen sind.

open System.Net
open Microsoft.FSharp.Control.WebExtensions
open System.Net.Http

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async {
        try
            let uri = new System.Uri(url)
            let httpClient = new HttpClient()
            let! html = httpClient.GetStringAsync(uri) |> Async.AwaitTask
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel
    |> Async.RunSynchronously
    |> ignore

runAll()

Weitere Informationen