Asynchrone expressies
In dit artikel wordt ondersteuning in F# beschreven voor asynchrone expressies. Asynchrone expressies bieden een manier om asynchroon berekeningen uit te voeren, dat wil gezegd, zonder de uitvoering van ander werk te blokkeren. Asynchrone berekeningen kunnen bijvoorbeeld worden gebruikt voor het schrijven van apps met UIS's die responsief blijven voor gebruikers terwijl de toepassing ander werk uitvoert. Met het programmeermodel F# Asynchrone werkstromen kunt u functionele programma's schrijven terwijl u de details van de threadovergang in een bibliotheek verbergt.
Asynchrone code kan ook worden gemaakt met behulp van taakexpressies, waarmee .NET-taken rechtstreeks worden gemaakt. Het gebruik van taakexpressies heeft de voorkeur wanneer u uitgebreid werkt met .NET-bibliotheken die .NET-taken maken of gebruiken. Bij het schrijven van de meeste asynchrone code in F# hebben F#-asynchrone expressies de voorkeur omdat ze beknopter, beter samengestelde en bepaalde beperkingen voorkomen die verbonden zijn met .NET-taken.
Syntaxis
async { expression }
Opmerkingen
In de vorige syntaxis is de berekening die wordt vertegenwoordigd door expression
ingesteld om asynchroon uit te voeren, dat wil zeggen, zonder dat de huidige computatiethread wordt geblokkeerd wanneer asynchrone slaapbewerkingen, I/O, en andere asynchrone bewerkingen worden uitgevoerd. Asynchrone berekeningen worden vaak gestart op een achtergrondthread terwijl de uitvoering van de huidige thread wordt voortgezet. Het type expressie wordt Async<'T>
, waarbij 'T
het type is dat door de expressie wordt geretourneerd wanneer het trefwoord return
wordt gebruikt.
De Async
-klasse biedt methoden die ondersteuning bieden voor verschillende scenario's. De algemene benadering is het maken van Async
objecten die de berekeningen of berekeningen vertegenwoordigen die u asynchroon wilt uitvoeren en deze berekeningen vervolgens starten met behulp van een van de triggerfuncties. De trigger die u gebruikt, is afhankelijk van of u de huidige thread, een achtergrondthread of een .NET-taakobject wilt gebruiken. Als u bijvoorbeeld een asynchrone berekening voor de huidige thread wilt starten, kunt u Async.StartImmediate
gebruiken. Wanneer u een asynchrone berekening start vanuit de UI-thread, blokkeert u de hoofdevenementlus niet die gebruikersacties, zoals toetsaanslagen en muisactiviteit, verwerkt, zodat uw toepassing responsief blijft.
Asynchrone binding met behulp van let!
In een asynchrone expressie zijn sommige expressies en bewerkingen synchroon en sommige zijn asynchroon. Wanneer u een methode asynchroon aanroept in plaats van een gewone let
binding, gebruikt u let!
. Het effect van let!
is dat het mogelijk maakt de uitvoering van andere berekeningen of threads te laten doorgaan terwijl de berekening wordt uitgevoerd. Nadat de rechterkant van de let!
-binding is teruggekeerd, wordt de uitvoering van de rest van de asynchrone expressie hervat.
De volgende code toont het verschil tussen let
en let!
. De coderegel die gebruikmaakt van let
maakt alleen een asynchrone berekening als een object dat u later kunt uitvoeren met bijvoorbeeld Async.StartImmediate
of Async.RunSynchronously
. De coderegel die gebruikmaakt van let!
start de berekening en voert een asynchrone wachttijd uit: de thread wordt onderbroken totdat het resultaat beschikbaar is, waarna de uitvoering wordt voortgezet.
// 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!
kan alleen worden gebruikt om rechtstreeks te wachten op F#-asynchrone berekeningen Async<T>
. U kunt indirect wachten op andere soorten asynchrone bewerkingen:
- .NET-taken, Task<TResult> en de niet-generieke Task, door te combineren met
Async.AwaitTask
- .NET-waardetaken, ValueTask<TResult> en de niet-generieke ValueTask, gecombineerd met
.AsTask()
enAsync.AwaitTask
- Elk object dat volgt op het patroon 'GetAwaiter' dat is opgegeven in F# RFC FS-1097, door te combineren met
task { return! expr } |> Async.AwaitTask
.
Controlestroom
Asynchrone expressies kunnen besturingsstroomconstructies bevatten, zoals for .. in .. do
, while .. do
, try .. with ..
, try .. finally ..
, if .. then .. else
en if .. then ..
. Deze kunnen op hun beurt verdere asynchrone constructies bevatten, met uitzondering van de with
en finally
handlers, die synchroon worden uitgevoerd.
F# asynchrone expressies bieden geen ondersteuning voor asynchrone try .. finally ..
. U kunt voor dit geval een taakexpressie gebruiken.
bindingen voor use
en use!
Binnen asynchrone expressies kunnen use
bindingen worden gekoppeld aan waarden van het type IDisposable. Voor deze laatste wordt de verwijderingsopschoonbewerking asynchroon uitgevoerd.
Naast let!
kunt u use!
gebruiken om asynchrone bindingen uit te voeren. Het verschil tussen let!
en use!
is hetzelfde als het verschil tussen let
en use
. Voor use!
wordt het object verwijderd aan het einde van het huidige bereik. Houd er rekening mee dat in de huidige versie van F# use!
niet toestaat dat een waarde wordt geïnitialiseerd op null, ook al is dat use
wel.
Asynchrone primitieven
Een methode die één asynchrone taak uitvoert en het resultaat retourneert, wordt een asynchrone primitievegenoemd. Deze zijn speciaal ontworpen voor gebruik met let!
. Verschillende asynchrone primitieven worden gedefinieerd in de F#-kernbibliotheek. Er worden twee methoden voor webtoepassingen gedefinieerd in de module FSharp.Control.WebExtensions
: WebRequest.AsyncGetResponse
en HttpClient.GetStringAsync (verpakt met Async.AwaitTask
voor compatibiliteit met het asynchrone model van F#). Beide primitieven downloaden gegevens van een webpagina, op basis van een URL.
AsyncGetResponse
een System.Net.WebResponse
object produceert en GetStringAsync
een tekenreeks produceert die de HTML voor een webpagina vertegenwoordigt.
Verschillende primitieven voor asynchrone I/O-bewerkingen zijn opgenomen in de module FSharp.Control.CommonExtensions
. Deze uitbreidingsmethoden van de klasse System.IO.Stream
zijn Stream.AsyncRead
en Stream.AsyncWrite
.
U kunt ook uw eigen asynchrone primitieven schrijven door een functie of methode te definiëren waarvan de hoofdtekst een asynchrone expressie is.
Als u asynchrone methoden wilt gebruiken in .NET Framework die zijn ontworpen voor andere asynchrone modellen met het Asynchrone F#-programmeermodel, maakt u een functie die een F#-Async
-object retourneert. De F#-bibliotheek heeft functies waarmee u dit eenvoudig kunt doen.
Hier ziet u een voorbeeld van het gebruik van asynchrone expressies; er zijn vele andere in de documentatie voor de methoden van de Async-klasse.
In dit voorbeeld ziet u hoe u asynchrone expressies gebruikt om code parallel uit te voeren.
In het volgende codevoorbeeld haalt een functie fetchAsync
de HTML-tekst op die wordt geretourneerd door een webaanvraag. De functie fetchAsync
bevat een asynchroon codeblok. Wanneer een binding wordt gemaakt met het resultaat van een asynchrone primitieve, in dit geval AsyncDownloadString
, wordt let!
gebruikt in plaats van let
.
U gebruikt de functie Async.RunSynchronously
om een asynchrone bewerking uit te voeren en te wachten op het resultaat. U kunt bijvoorbeeld meerdere asynchrone bewerkingen parallel uitvoeren met behulp van de functie Async.Parallel
samen met de functie Async.RunSynchronously
. De functie Async.Parallel
gebruikt een lijst met de Async
objecten, stelt de code in voor elk Async
taakobject dat parallel moet worden uitgevoerd en retourneert een Async
-object dat de parallelle berekening vertegenwoordigt. Net als voor één bewerking roept u Async.RunSynchronously
aan om de uitvoering te starten.
De runAll
-functie start drie asynchrone expressies parallel en wacht totdat ze allemaal zijn voltooid.
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()