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() en Async.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 .. elseen 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

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

Zie ook