Asynchrone Workflows (F#)

In diesem Thema wird die Unterstützung in F# zum Ausführen von asynchronen Berechnungen beschrieben, d. h., ohne die Ausführung anderer Vorgänge zu blockieren. Asynchrone Berechnungen können z. B. verwendet werden, um Anwendungen mit Benutzeroberflächen zu schreiben, die während der Ausführung anderer Vorgänge weiterhin auf die Eingabe des Benutzers reagieren können.

async { expression }

Hinweise

In der vorherigen Syntax wird die durch expression dargestellte Berechnung für die asynchrone Ausführung eingerichtet, d. h. ohne den aktuellen Berechnungsthread zu blockieren, wenn asynchrone Standby-Vorgänge, E/A- und andere asynchrone Vorgänge ausgeführt werden. Asynchrone Berechnungen werden oft in einem Hintergrundthread gestartet, während die Ausführung im aktuellen Thread fortgesetzt wird. Der Ausdruckstyp ist Async<'a>, wobei 'a der Typ ist, der bei Verwendung des return-Schlüsselworts vom Ausdruck zurückgegeben wird. Der Code in solch einem Ausdruck wird als asynchroner Block oder async-Block bezeichnet.

Es gibt eine Vielzahl von Methoden zum asychronen Programmieren, und die Async-Klasse stellt Methoden bereit, die mehrere Szenarien unterstützen. Der allgemeine Ansatz ist, Async-Objekte zu erstellen, die die asynchron auszuführende(n) Berechnung oder Berechnungen darstellen, und diese Berechnungen mit einer der auslösenden Funktionen zu starten. Die verschiedenen auslösenden Funktionen bieten andere Möglichkeiten, asynchrone Berechnungen auszuführen. Welche Sie verwenden, hängt davon ab, ob Sie den aktuellen Thread, einen Hintergrundthread oder ein .NET Framework-Taskobjekt verwenden möchten und ob es Fortsetzungsfunktionen gibt, die ausgeführt werden sollen, wenn die Berechnung beendet wird. Beispielsweise können Sie eine asynchrone Berechnung mithilfe von Async.StartImmediate auf dem aktuellen Thread starten. Wenn Sie eine asynchrone Berechnung vom UI-Thread starten, wird die Hauptereignisschleife, die Benutzeraktionen (z. B. Tastaturanschläge und Mausaktivität) verarbeitet, nicht blockiert und die Anwendung reagiert weiterhin.

Asynchrone Bindung mithilfe von let!

In einem asynchronen Workflow sind einige Ausdrücke und Vorgänge synchron, und bei einigen handelt es sich um längere Berechnungen, die das Ergebnis asynchron ausgeben. Wenn Sie statt einer gewöhnlichen let-Bindung eine Methode asynchron aufrufen, verwenden Sie let!. Der Zweck von let! ist, dass andere Berechnungen oder Threads weiter ausgeführt werden können, während die Berechnung ausgeführt wird. Nachdem die rechte Seite der let!-Bindung ausgeführt wurde, wird der Rest des asynchronen Workflows fortgesetzt.

Im folgenden Code wird der Unterschied zwischen let und let! veranschaulicht. Die Codezeile, die let verwendet, erstellt eine asynchrone Berechnung als Objekt, das später z. B. mit Async.StartImmediate oder Async.RunSynchronously ausgeführt werden kann. Die Codezeile, die let! verwendet, startet die Berechnung. Anschließend wird der Thread angehalten, bis das Ergebnis verfügbar ist. An diesem Punkt wird mit der Ausführung fortgefahren.

// 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)

Zusätzlich zu let! können Sie use! verwenden, um asynchrone Bindungen auszuführen. Der Unterschied zwischen let! und use! entspricht dem Unterschied zwischen let und use. Bei use! wird das Objekt am Ende des aktuellen Gültigkeitsbereichs freigegeben. Beachten Sie, dass in der aktuellen Version der Programmiersprache F# für use! kein Wert auf NULL initialisiert werden kann, obwohl dies für use möglich ist.

Asynchrone Primitiven

Eine Methode, die eine einzelne asynchrone Aufgabe ausführt und das Ergebnis zurückgibt, wird als asynchrone Primitive bezeichnet. Diese werden ausdrücklich zur Verwendung mit let! entworfen. Mehrere asynchrone Primitiven sind in der F#-Kernbibliothek definiert. Zwei solche Methoden für Webanwendungen werden im Microsoft.FSharp.Control.WebExtensions-Modul definiert: WebRequest.AsyncGetResponse und WebClient.AsyncDownloadString. Beide Primitiven laden Daten von einer Webseite anhand einer URL herunter. AsyncGetResponse erzeugt ein WebResponse-Objekt, und AsyncDownloadString erzeugt eine Zeichenfolge, die die HTML für eine Webseite darstellt.

Mehrere Primitiven für asynchrone E/A-Vorgänge sind im Microsoft.FSharp.Control.CommonExtensions-Modul enthalten. Diese Erweiterungsmethoden der Stream-Klasse sind Stream.AsyncRead und Stream.AsyncWrite.

Zusätzliche asynchrone Primitiven sind im F#-PowerPack verfügbar. Sie können auch eigene asynchrone Primitiven schreiben, indem Sie eine Funktion definieren, deren vollständiger Text in einem async-Block enthalten ist.

Zur Verwendung asynchroner Methoden in .NET Framework, die für andere asynchrone Modelle mit dem asynchronen F#-Programmiermodell entworfen wurden, erstellen Sie eine Funktion, die ein Async-Objekt in F# zurückgibt. Die F#-Bibliothek verfügt über Funktionen, mit denen dies leicht zu bewerkstelligen ist.

Ein Beispiel für die Verwendung asynchroner Workflows ist hier enthalten. Es gibt viele weitere in der Dokumentation für die Methoden der Async-Klasse.

Beispiel

In diesem Beispiel wird gezeigt, wie asynchrone Workflows zur parallelen Berechnung verwendet werden.

Im folgenden Codebeispiel wird eine fetchAsync-Funktion von einer Webanforderung dem HTML-Text zurückgegeben. Die fetchAsync-Funktion enthält einen asynchronen Codeblock. Wenn eine Bindung zum Ergebnis eines asynchronen Primitiven erstellt wird, in diesem Fall AsyncDownloadString, wird let! statt let verwendet.

Mit der Funktion Async.RunSynchronously wird ein asynchroner Vorgang ausgeführt und auf das Ergebnis gewartet. Sie können mehrere asynchrone Vorgänge gleichzeitig ausführen, indem Sie die Async.Parallel-Funktion zusammen mit der Async.RunSynchronously-Funktion verwenden. Die Async.Parallel-Funktion akzeptiert eine Liste der Async-Objekte, richtet den Code für jedes Async-Aufgabenobjekt zur parallelen Ausführung ein und gibt ein Async-Objekt zurück, das die gleichzeitige Berechnung darstellt. Sie rufen Async.RunSynchronously wie für einen einzelnen Vorgang auf, um die Ausführung zu starten.

Die runAll-Funktion startet drei asynchrone Workflows parallel und wartet, bis alle abgeschlossen wurden.

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

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

let fetchAsync(name, url:string) =
    async { 
        try
            let uri = new System.Uri(url)
            let webClient = new WebClient()
            let! html = webClient.AsyncDownloadString(uri)
            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()

Siehe auch

Weitere Ressourcen

F#-Sprachreferenz

Berechnungsausdrücke (F#)

Control.Async-Klasse (F#)

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

Oktober 2010

Erläutert, dass nicht alle asynchronen Vorgänge in einem Hintergrundthread auftreten.

Informationsergänzung.