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, waardoor .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, meer samenstelling zijn en bepaalde opmerkingen voorkomen die zijn gekoppeld aan .NET-taken.
Syntaxis
async { expression }
Opmerkingen
In de vorige syntaxis wordt de berekening die wordt expression
vertegenwoordigd door asynchroon uitgevoerd, dus zonder de huidige berekeningsthread te blokkeren 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 van de expressie is Async<'T>
, waar 'T
is het type dat wordt geretourneerd door de expressie wanneer het return
trefwoord wordt gebruikt.
De Async
klasse biedt methoden die ondersteuning bieden voor verschillende scenario's. De algemene benadering is het maken Async
van 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 gebruiken Async.StartImmediate
. Wanneer u een asynchrone berekening start vanuit de ui-thread, blokkeert u de hoofd-gebeurtenislus 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 let!
u . Het effect is let!
dat de uitvoering kan worden voortgezet op andere berekeningen of threads terwijl de berekening wordt uitgevoerd. Nadat de rechterkant van de let!
binding wordt geretourneerd, wordt de uitvoering hervat door de rest van de asynchrone expressie.
De volgende code toont het verschil tussen let
en let!
. Met de coderegel die let
alleen een asynchrone berekening maakt als een object dat u later kunt uitvoeren, bijvoorbeeldAsync.StartImmediate
.Async.RunSynchronously
De coderegel die gebruikmaakt let!
van start de berekening en voert een asynchrone wachttijd uit: de thread wordt onderbroken totdat het resultaat beschikbaar is, waarna de uitvoering van het punt 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 op F#-asynchrone berekeningen Async<T>
te wachten. U kunt indirect wachten op andere soorten asynchrone bewerkingen:
- .NET-taken Task<TResult> en de niet-algemene Tasktaken door te combineren met
Async.AwaitTask
- .NET-waardetaken en ValueTask<TResult> de niet-algemene ValueTasktaken door te combineren 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
, , try .. with ..
while .. do
, try .. finally ..
, , if .. then .. else
en if .. then ..
. Deze kunnen op zijn 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 een taakexpressie gebruiken voor dit geval.
use
en use!
bindingen
Binnen asynchrone expressies kunnen bindingen use
worden gekoppeld aan waarden van het type IDisposable. Voor deze laatste wordt de verwijderingsopschoonbewerking asynchroon uitgevoerd.
Daarnaast let!
kunt use!
u asynchrone bindingen uitvoeren. Het verschil tussen let!
en use!
is hetzelfde als het verschil tussen let
en use
. Voor use!
, het object wordt verwijderd aan het einde van het huidige bereik. Houd er rekening mee dat in de huidige versie van F# use!
geen waarde mag worden geïnitialiseerd naar null, ook al use
is dat wel het geval.
Asynchrone primitieven
Een methode die één asynchrone taak uitvoert en het resultaat retourneert, wordt een asynchrone primitieve genoemd. Deze zijn speciaal ontworpen voor gebruik met let!
. Verschillende asynchrone primitieven worden gedefinieerd in de F#-kernbibliotheek. Twee methoden voor webtoepassingen worden gedefinieerd in de module FSharp.Control.WebExtensions
: WebRequest.AsyncGetResponse
en WebClient.AsyncDownloadString
. Beide primitieven downloaden gegevens van een webpagina, op basis van een URL. AsyncGetResponse
produceert een System.Net.WebResponse
object en AsyncDownloadString
produceert een tekenreeks die de HTML voor een webpagina vertegenwoordigt.
Verschillende primitieven voor asynchrone I/O-bewerkingen zijn opgenomen in de FSharp.Control.CommonExtensions
module. Deze uitbreidingsmethoden van de System.IO.Stream
klasse 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 programmeermodel F#, 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 uit een webaanvraag. De fetchAsync
functie bevat een asynchroon codeblok. Wanneer een binding wordt gemaakt met het resultaat van een asynchrone primitieve, in dit geval AsyncDownloadString
, let!
wordt 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 Async.Parallel
functie samen met de Async.RunSynchronously
functie. De Async.Parallel
functie gebruikt een lijst met de Async
objecten, stelt de code in voor elk Async
taakobject dat parallel wordt uitgevoerd en retourneert een Async
object dat de parallelle berekening vertegenwoordigt. Net als voor één bewerking roept Async.RunSynchronously
u 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()