Asynkrona uttryck
Den här artikeln beskriver stöd i F# för asynkrona uttryck. Asynkrona uttryck ger ett sätt att utföra beräkningar asynkront, det vill: utan att blockera körning av annat arbete. Till exempel kan asynkrona beräkningar användas för att skriva appar som har UIs som förblir dynamiska för användare när programmet utför annat arbete. Med programmeringsmodellen F# Asynkrona arbetsflöden kan du skriva funktionella program samtidigt som du döljer information om trådövergången i ett bibliotek.
Asynkron kod kan också redigeras med hjälp av uppgiftsuttryck som skapar .NET-uppgifter direkt. Användning av uppgiftsuttryck är att föredra när du samverkar i stor utsträckning med .NET-bibliotek som skapar eller använder .NET-uppgifter. När du skriver de flesta asynkrona kod i F# föredras Asynkrona F#-uttryck eftersom de är mer kortfattade, mer kompositionsbaserade och undviker vissa varningar som är associerade med .NET-uppgifter.
Syntax
async { expression }
Kommentarer
I den tidigare syntaxen är den beräkning som representeras av expression
konfigurerad för att köras asynkront, det vill ex utan att blockera den aktuella beräkningstråden när asynkrona viloåtgärder, I/O och andra asynkrona åtgärder utförs. Asynkrona beräkningar startas ofta i en bakgrundstråd medan körningen fortsätter på den aktuella tråden. Uttryckets typ är Async<'T>
, där 'T
är den typ som returneras av uttrycket när nyckelordet return
används.
Klassen Async
innehåller metoder som stöder flera scenarier. Den allmänna metoden är att skapa Async
objekt som representerar den beräkning eller de beräkningar som du vill köra asynkront och sedan starta dessa beräkningar med någon av de utlösande funktionerna. Vilken utlösare du använder beror på om du vill använda den aktuella tråden, en bakgrundstråd eller ett .NET-aktivitetsobjekt. Om du till exempel vill starta en asynkron beräkning på den aktuella tråden kan du använda Async.StartImmediate
. När du startar en asynkron beräkning från användargränssnittstråden blockerar du inte huvudhändelseslingan som bearbetar användaråtgärder som tangenttryckningar och musaktivitet, så att programmet förblir responsivt.
Asynkron bindning med hjälp av let!
I ett asynkront uttryck är vissa uttryck och åtgärder synkrona och vissa är asynkrona. När du anropar en metod asynkront använder let!
du i stället för en vanlig let
bindning . Effekten av let!
är att aktivera körning för att fortsätta på andra beräkningar eller trådar när beräkningen utförs. När den högra sidan av bindningen let!
har returnerats återupptas körningen av resten av asynkrona uttrycket.
Följande kod visar skillnaden mellan let
och let!
. Kodraden som använder let
skapar bara en asynkron beräkning som ett objekt som du kan köra senare med hjälp av till exempel Async.StartImmediate
eller Async.RunSynchronously
. Kodraden som använder let!
startar beräkningen och utför en asynkron väntan: tråden pausas tills resultatet är tillgängligt, då körningen fortsätter.
// 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 endast användas för att invänta F#-asynkrona Async<T>
beräkningar direkt. Du kan invänta andra typer av asynkrona åtgärder indirekt:
- .NET-uppgifter Task<TResult> och icke-generiska Taskgenom att kombinera med
Async.AwaitTask
- .NET-värdeuppgifter ValueTask<TResult> och icke-generiska ValueTaskgenom att kombinera med
.AsTask()
ochAsync.AwaitTask
- Alla objekt som följer "GetAwaiter"-mönstret som anges i F# RFC FS-1097, genom att kombinera med
task { return! expr } |> Async.AwaitTask
.
Kontrollflöde
Asynkrona uttryck kan innehålla konstruktioner för kontrollflöde, till exempel for .. in .. do
, while .. do
, try .. with ..
, try .. finally ..
, if .. then .. else
och if .. then ..
. Dessa kan i sin tur omfatta ytterligare asynkrona konstruktioner, med undantag för with
hanterare och finally
som körs synkront.
Asynkrona F#-uttryck stöder inte asynkrona try .. finally ..
. Du kan använda ett uppgiftsuttryck för det här fallet.
use
och use!
bindningar
Inom asynkrona use
uttryck kan bindningar binda till värden av typen IDisposable. För det senare körs rensningsåtgärden asynkront.
Förutom let!
kan du använda use!
för att utföra asynkrona bindningar. Skillnaden mellan let!
och use!
är densamma som skillnaden mellan let
och use
. För use!
tas objektet bort i slutet av det aktuella omfånget. Observera att i den aktuella versionen av F#, use!
tillåter inte att ett värde initieras till null, även om use
det gör det.
Asynkrona primitiver
En metod som utför en enda asynkron uppgift och returnerar resultatet kallas för en asynkron primitiv, och de är särskilt utformade för användning med let!
. Flera asynkrona primitiver definieras i F#-kärnbiblioteket. Två sådana metoder för webbprogram definieras i modulen FSharp.Control.WebExtensions
: WebRequest.AsyncGetResponse
och WebClient.AsyncDownloadString
. Båda primitiverna laddar ned data från en webbsida, givet en URL. AsyncGetResponse
skapar ett System.Net.WebResponse
objekt och AsyncDownloadString
skapar en sträng som representerar HTML-koden för en webbsida.
Flera primitiver för asynkrona I/O-åtgärder ingår i modulen FSharp.Control.CommonExtensions
. Dessa tilläggsmetoder för System.IO.Stream
klassen är Stream.AsyncRead
och Stream.AsyncWrite
.
Du kan också skriva egna asynkrona primitiver genom att definiera en funktion eller metod vars brödtext är ett asynkront uttryck.
Om du vill använda asynkrona metoder i .NET Framework som är utformade för andra asynkrona modeller med den asynkrona F#-programmeringsmodellen skapar du en funktion som returnerar ett F#- Async
objekt. F#-biblioteket har funktioner som gör det enkelt att göra det.
Ett exempel på hur du använder asynkrona uttryck ingår här. det finns många andra i dokumentationen för metoderna i klassen Async.
Det här exemplet visar hur du använder asynkrona uttryck för att köra kod parallellt.
I följande kodexempel hämtar en funktion fetchAsync
HTML-texten som returneras från en webbbegäran. Funktionen fetchAsync
innehåller ett asynkront kodblock. När en bindning görs till resultatet av en asynkron primitiv används i det här fallet AsyncDownloadString
let!
i stället för let
.
Du använder funktionen Async.RunSynchronously
för att köra en asynkron åtgärd och vänta på resultatet. Du kan till exempel köra flera asynkrona åtgärder parallellt med hjälp Async.Parallel
av funktionen tillsammans med Async.RunSynchronously
funktionen. Funktionen Async.Parallel
tar en lista över objekten Async
, konfigurerar koden för varje Async
aktivitetsobjekt som ska köras parallellt och returnerar ett Async
objekt som representerar den parallella beräkningen. Precis som för en enda åtgärd anropar Async.RunSynchronously
du för att starta körningen.
Funktionen runAll
startar tre asynkrona uttryck parallellt och väntar tills alla har slutförts.
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()