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.StartImmediate
verwenden. 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.RunSynchronously
ausfü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()
undAsync.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.WebExtensions
definiert: 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()