Espressioni asincrone

Questo articolo descrive il supporto in F# per le espressioni asincrone. Le espressioni asincrone offrono un modo per eseguire calcoli in modo asincrono, ovvero senza bloccare l'esecuzione di altre operazioni. Ad esempio, i calcoli asincroni possono essere usati per scrivere app con interfacce utente che rimangono reattive agli utenti mentre l'applicazione esegue altre operazioni. Il modello di programmazione F# Asynchronous Workflow consente di scrivere programmi funzionali nascondendo i dettagli della transizione di thread all'interno di una libreria.

È anche possibile creare codice asincrono usando espressioni di attività, che creano direttamente attività .NET. L'uso delle espressioni di attività è preferibile quando si interagisce ampiamente con le librerie .NET che creano o usano attività .NET. Quando si scrive la maggior parte del codice asincrono in F#, è preferibile usare espressioni asincrone F# perché sono più concise, più compositionali ed evitare alcune avvertenze associate alle attività .NET.

Sintassi

async { expression }

Osservazioni:

Nella sintassi precedente, il calcolo rappresentato da expression viene configurato per l'esecuzione asincrona, ovvero senza bloccare il thread di calcolo corrente quando vengono eseguite operazioni di sospensione asincrone, I/O e altre operazioni asincrone. I calcoli asincroni vengono spesso avviati in un thread in background mentre l'esecuzione continua nel thread corrente. Il tipo dell'espressione è Async<'T>, dove 'T è il tipo restituito dall'espressione quando viene usata la return parola chiave .

La Async classe fornisce metodi che supportano diversi scenari. L'approccio generale consiste nel creare Async oggetti che rappresentano il calcolo o i calcoli da eseguire in modo asincrono e quindi avviare questi calcoli usando una delle funzioni di attivazione. L'attivazione utilizzata dipende dal fatto che si voglia usare il thread corrente, un thread in background o un oggetto attività .NET. Ad esempio, per avviare un calcolo asincrono nel thread corrente, è possibile usare Async.StartImmediate. Quando si avvia un calcolo asincrono dal thread dell'interfaccia utente, non si blocca il ciclo di eventi principale che elabora azioni utente come le sequenze di tasti e l'attività del mouse, in modo che l'applicazione rimanga reattiva.

Associazione asincrona tramite let!

In un'espressione asincrona alcune espressioni e operazioni sono sincrone e alcune sono asincrone. Quando si chiama un metodo in modo asincrono, anziché un'associazione normale let , si usa let!. L'effetto di let! è consentire all'esecuzione di continuare su altri calcoli o thread durante l'esecuzione del calcolo. Al termine della restituzione del lato destro dell'associazione let! , il resto dell'espressione asincrona riprende l'esecuzione.

Il codice seguente illustra la differenza tra let e let!. La riga di codice che usa let crea semplicemente un calcolo asincrono come oggetto che è possibile eseguire in un secondo momento usando, ad esempio, Async.StartImmediate o Async.RunSynchronously. La riga di codice che usa let! avvia il calcolo ed esegue un'attesa asincrona: il thread viene sospeso fino a quando il risultato non è disponibile, in corrispondenza del quale l'esecuzione del punto continua.

// 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! può essere usato solo per attendere direttamente i calcoli asincroni Async<T> F#. È possibile attendere altri tipi di operazioni asincrone indirettamente:

  • Attività .NET Task<TResult> e non generici Task, combinando con Async.AwaitTask
  • Attività di valore ValueTask<TResult> .NET e non generici ValueTask, combinando con .AsTask() e Async.AwaitTask
  • Qualsiasi oggetto che segue il modello "GetAwaiter" specificato in F# RFC FS-1097, combinando con task { return! expr } |> Async.AwaitTask.

Flusso di controllo

Le espressioni asincrone possono includere costrutti del flusso di controllo, ad esempio for .. in .. do, try .. finally ..if .. then .. elsewhile .. dotry .. with ..e .if .. then .. Questi possono, a loro volta, includere ulteriori costrutti asincroni, ad eccezione dei with gestori e finally , che vengono eseguiti in modo sincrono.

Le espressioni asincrone try .. finally ..F# non supportano . È possibile usare un'espressione di attività per questo caso.

useassociazioni e use!

All'interno di espressioni asincrone, use i binding possono essere associati ai valori di tipo IDisposable. Per quest'ultimo, l'operazione di pulizia dello smaltimento viene eseguita in modo asincrono.

Oltre a let!, è possibile usare use! per eseguire associazioni asincrone. La differenza tra let! e use! corrisponde alla differenza tra let e .use Per use!, l'oggetto viene eliminato alla chiusura dell'ambito corrente. Si noti che nella versione corrente di F#, use! non consente l'inizializzazione di un valore su Null, anche se use lo fa.

Primitive asincrone

Un metodo che esegue una singola attività asincrona e restituisce il risultato viene chiamato primitiva asincrona e sono progettati specificamente per l'uso con let!. Nella libreria principale F# sono definite diverse primitive asincrone. Due metodi di questo tipo per le applicazioni Web sono definiti nel modulo FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse e WebClient.AsyncDownloadString. Entrambe le primitive scaricano i dati da una pagina Web, dato un URL. AsyncGetResponse produce un System.Net.WebResponse oggetto e AsyncDownloadString produce una stringa che rappresenta il codice HTML per una pagina Web.

Nel modulo sono incluse FSharp.Control.CommonExtensions diverse primitive per le operazioni di I/O asincrone. Questi metodi di estensione della System.IO.Stream classe sono Stream.AsyncRead e Stream.AsyncWrite.

È anche possibile scrivere primitive asincrone definendo una funzione o un metodo il cui corpo è un'espressione asincrona.

Per usare metodi asincroni in .NET Framework progettati per altri modelli asincroni con il modello di programmazione asincrona F#, creare una funzione che restituisce un oggetto F# Async . La libreria F# include funzioni che semplificano questa operazione.

Un esempio di uso di espressioni asincrone è incluso qui; nella documentazione sono disponibili molti altri metodi della classe Async.

In questo esempio viene illustrato come usare espressioni asincrone per eseguire codice in parallelo.

Nell'esempio di codice seguente una funzione fetchAsync ottiene il testo HTML restituito da una richiesta Web. La fetchAsync funzione contiene un blocco asincrono di codice. Quando viene eseguita un'associazione al risultato di una primitiva asincrona, in questo caso AsyncDownloadString, let! viene usata invece di let.

Usare la funzione Async.RunSynchronously per eseguire un'operazione asincrona e attendere il risultato. Ad esempio, è possibile eseguire più operazioni asincrone in parallelo usando la Async.Parallel funzione insieme alla Async.RunSynchronously funzione . La Async.Parallel funzione accetta un elenco degli Async oggetti, imposta il codice per ogni Async oggetto attività da eseguire in parallelo e restituisce un Async oggetto che rappresenta il calcolo parallelo. Proprio come per una singola operazione, si chiama Async.RunSynchronously per avviare l'esecuzione.

La runAll funzione avvia tre espressioni asincrone in parallelo e attende il completamento di tutte le espressioni.

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

Vedi anche