Asynchrone Ausdrücke

In diesem Artikel wird die Unterstützung für asynchrone Ausdrücke in F# beschrieben. Mit asynchronen Ausdrücken können Berechnungen asynchron ausgeführt werden, d. h. ohne das Durchführen anderer Aufgaben zu blockieren. Beispielsweise können asynchrone Berechnungen verwendet werden, um Apps zu schreiben, deren Benutzeroberflächen auch dann noch reagieren, wenn die Anwendung andere Aufgaben ausführt. Mit dem Programmiermodell für asynchrone F#-Workflows können Sie funktionale Programme schreiben und dabei die Details des Threadübergangs in einer Bibliothek verstecken.

Asynchroner Code kann auch mithilfe von Taskausdrücken erstellt werden. So entstehen .NET-Aufgaben direkt. Die Verwendung von Taskausdrücken wird bevorzugt, wenn eine umfangreiche Zusammenarbeit mit .NET-Bibliotheken erfolgt, die .NET-Tasks 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 }

Bemerkungen

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 und die Ausführung im aktuellen Thread fortgesetzt. Als Ausdruckstyp wird Async<'T> verwendet, wobei 'T der Typ ist, der vom Ausdruck zurückgegeben wird, wenn das Schlüsselwort return verwendet wird.

Mit der Async-Klasse werden Methoden bereitgestellt, die mehrere Szenarios unterstützen. Der allgemeine Ansatz besteht darin, Async-Objekte zu erstellen, die die Berechnungen darstellen, die Sie asynchron ausführen möchten. Starten Sie diese dann mithilfe einer der Triggerfunktionen. Welchen Trigger Sie verwenden hängt davon ab, ob Sie den aktuellen Thread, einen Hintergrundthread oder ein .NET-Aufgabenobjekt nutzen möchten. Um beispielsweise eine asynchrone Berechnung im aktuellen Thread zu starten, können Sie Async.StartImmediate verwenden. Wenn Sie eine asynchrone Berechnung über den UI-Thread starten, blockieren Sie die Hauptereignisschleife nicht, die Benutzeraktionen wie Tastatureingaben und Mausaktivitäten verarbeitet, sodass Ihre Anwendung weiterhin reagiert.

Asynchrone Bindung mithilfe von let!

In einem asynchronen Ausdruck sind einige Ausdrücke und Vorgänge synchron, andere asynchron. Wenn Sie eine Methode asynchron aufrufen, verwenden Sie let! anstelle einer gewöhnlichen let-Bindung . 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.

Anhand des folgenden Codes wird der Unterschied zwischen let und let! deutlich. Die Codezeile, die let verwendet, erstellt lediglich eine asynchrone Berechnung als Objekt, das Sie später ausführen können, z. B indem Sie Async.StartImmediate oder Async.RunSynchronously nutzen. Die Codezeile, die let! verwendet, startet die Berechnung und führt einen asynchronen Wartevorgang aus: Der Thread wird angehalten, bis das Ergebnis verfügbar ist. An diesem Punkt wird die Ausführung fortgesetzt.

// 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#-Berechnungen zu warten Async<T>. Sie können indirekt auf andere Arten von asynchronen Vorgängen 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 die Entsorgungsbereinigung asynchron ausgeführt.

Neben let! können Sie auch use! verwenden, um asynchrone Bindungen durchzuführen. Der Unterschied zwischen let! und use! ist der gleiche wie der 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! das Initialisieren eines Werts mit NULL nicht zulässt, obwohl dies bei use der Fall ist.

Asynchrone Grundtypen

Eine Methode, die eine einzelne asynchrone Aufgabe ausführt und das Ergebnis zurückgibt, wird als asynchroner Grundtyp bezeichnet. Diese Methoden sind speziell für die Verwendung mit let! konzipiert. In der F#-Kernbibliothek sind mehrere asynchrone Grundtypen definiert. Zwei solche Methoden für Webanwendungen sind im Modul FSharp.Control.WebExtensions definiert: WebRequest.AsyncGetResponse und WebClient.AsyncDownloadString. Beide Grundtypen laden unter Angabe einer URL Daten von einer Webseite herunter. AsyncGetResponse erzeugt ein System.Net.WebResponse-Objekt und AsyncDownloadString eine Zeichenfolge, die den HTML-Code für eine Webseite darstellt.

Das Modul FSharp.Control.CommonExtensions enthält mehrere Grundtypen für asynchrone E/A-Vorgänge. Bei diesen Erweiterungsmethoden der System.IO.Stream-Klasse handelt es sich um 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 im .NET Framework zu verwenden, die für andere asynchrone Modelle mit dem asynchronen F#-Programmiermodell entwickelt wurden, erstellen Sie eine Funktion, die ein Async-Objekt für F# zurückgibt. Die F#-Bibliothek verfügt über Funktionen, die dies vereinfachen.

Ein Beispiel für das Verwenden von asynchronen Ausdrücken finden Sie hier. In der Dokumentation für die Methoden der Async-Klasse sind viele weitere enthalten.

In diesem Beispiel wird gezeigt, wie Sie asynchrone Ausdrücke verwenden, um Code parallel auszuführen.

Im folgenden Codebeispiel ruft eine fetchAsync-Funktion den HTML-Text ab, der von einer Webanforderung zurückgegeben wurde. 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 Async.RunSynchronously-Funktion, um einen asynchronen Vorgang auszuführen und auf dessen Ergebnis zu warten. Beispielsweise 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 akzeptiert 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

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