Flussi di lavoro asincroni (F#)

In questo argomento viene descritto il supporto in F# per l'esecuzione asincrona di calcoli, ovvero, senza bloccare l'esecuzione di altre attività. È ad esempio possibile utilizzare i calcoli asincroni per scrivere applicazioni con interfacce utente che rimangono attive per gli utenti anche mentre l'applicazione esegue altre attività.

async { expression }

Note

Nella sintassi precedente il calcolo rappresentato da expression viene impostato per eseguire in modo asincrono, ovvero, senza bloccare il thread di calcolo corrente quando vengono eseguite le operazioni di sospensione asincrone, I/O e altre operazioni asincrone. I calcoli asincroni sono spesso avviati su un thread in background mentre l'esecuzione continua sul thread corrente. Il tipo dell'espressione è Async<'a>, in cui 'a è il tipo restituito dall'espressione nel caso in cui venga utilizzata la parola chiave return. Al codice in tale espressione viene fatto riferimento come blocco asincrono o blocco async.

Esistono varie modalità di programmazione asincrona e la classe Async fornisce metodi che supportano vari scenari. L'approccio generale consiste nel creare oggetti Async che rappresentano uno o più calcoli che si desidera eseguire in modo asincrono e nell'avviare quindi questi calcoli tramite una delle funzioni di attivazione. Le varie funzioni di attivazione forniscono vari metodi di esecuzione di calcoli asincroni. Il metodo impiegato dipende dall'utilizzo del thread corrente, di un thread in background o di un oggetto di attività .NET Framework e dalla presenza di funzioni di continuazione da eseguire al termine del calcolo. Per avviare ad esempio un calcolo asincrono sul thread corrente, è possibile utilizzare Async.StartImmediate. Se si avvia un calcolo asincrono dal thread UI, il ciclo dell'evento principale che elabora azioni utente, ad esempio sequenze di tasti e attività del mouse, non viene bloccato e pertanto l'applicazione rimane attiva.

Associazione asincrona tramite let!

In un flusso di lavoro asincrono alcune espressioni e operazioni sono sincrone, mentre altre costituiscono calcoli più lunghi progettati per restituire un risultato in modo asincrono. Quando si chiama un metodo in modo asincrono, anziché un'associazione let comune si utilizza let!. L'utilizzo di let! consente il proseguimento dell'esecuzione su altri calcoli o thread durante l'esecuzione del calcolo. Dopo che il lato destro dell'associazione let! ha restituito un risultato, viene ripresa l'esecuzione della parte restante del flusso di lavoro asincrono.

Nell'esempio di codice riportato di seguito viene illustrata la differenza tra let e let!. La riga di codice che utilizza let crea semplicemente un calcolo asincrono come un oggetto che è possibile eseguire in un secondo momento, ad esempio, Async.StartImmediate o Async.RunSynchronously. La riga di codice che utilizza let! avvia il calcolo, quindi il thread viene sospeso finché il risultato non è disponibile. A questo punto, l'esecuzione procede.

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

Oltre a let!, è possibile utilizzare use! per eseguire le associazioni asincrone. La differenza tra let! e use! è la stessa che esiste tra let e use. Per use!, l'oggetto viene eliminato al termine dell'ambito corrente. Si noti che nella versione corrente del linguaggio F#, use! non consente a un valore di essere inizializzato su null, anche se use lo fa.

Primitivi asincroni

Un metodo che esegue una sola attività asincrona e restituisce il risultato viene denominato primitivo asincrono ed è concepito in modo specifico per l'utilizzo con let!. Nella libreria di base di F# vengono definiti diversi primitivi asincroni. Nel modulo Microsoft.FSharp.Control.WebExtensions vengono definiti due metodi di questo tipo per le applicazioni Web: WebRequest.AsyncGetResponse e WebClient.AsyncDownloadString. Entrambi i primitivi scaricano dati da una pagina Web, in base a un URL specifico. AsyncGetResponse produce un oggetto WebResponse e AsyncDownloadString produce una stringa che rappresenta il codice HTML per una pagina Web.

Nel modulo Microsoft.FSharp.Control.CommonExtensions sono inclusi diversi primitivi per le operazioni di I/O asincrone. Questi metodi di estensione della classe Stream sono Stream.AsyncRead e Stream.AsyncWrite.

Ulteriori primitivi asincroni sono disponibili in F# PowerPack. È inoltre possibile scrivere primitivi asincroni personalizzati definendo una funzione il cui corpo completo è incluso nel blocco async.

Per utilizzare metodi asincroni in .NET Framework progettati per altri modelli asincroni con il modello di programmazione asincrono di F#, è necessario creare una funzione che restituisce un oggetto F# Async. La libreria F# dispone di funzioni che agevolano questa attività.

Un esempio dell'utilizzo dei flussi di lavoro asincroni è incluso di seguito. ne esistono molti altri nella documentazione per i metodi della classe Async.

Esempio

In questo esempio viene mostrato come utilizzare flussi di lavoro asincroni per eseguire calcoli in parallelo.

Nell'esempio di codice seguente, una funzione fetchAsync ottiene il testo HTML restituito da una richiesta Web. La funzione fetchAsync contiene un blocco di codice asincrono. Se viene eseguita un'associazione al risultato di un primitivo asincrono, in questo caso AsyncDownloadString, viene utilizzato let! anziché let.

Per eseguire un'operazione asincrona e attendere il risultato, utilizzare la funzione Async.RunSynchronously. È ad esempio possibile eseguire più operazioni asincrone in parallelo mediante la funzione Async.Parallel insieme alla funzione Async.RunSynchronously. La funzione Async.Parallel utilizza un elenco degli oggetti Async, configura il codice per ogni oggetto di attività Async da eseguire in parallelo e restituisce un oggetto Async che rappresenta il calcolo parallelo. Analogamente a quanto avviene per una singola operazione, per avviare l'esecuzione è necessario chiamare Async.RunSynchronously.

La funzione runAll avvia tre flussi di lavoro asincroni in parallelo e ne attende il completamento.

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
                "MSDN", "https://msdn.microsoft.com/"
                "Bing", "https://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()

Vedere anche

Riferimenti

Classe Control.Async (F#)

Altre risorse

Riferimenti per il linguaggio F#

Espressioni di calcolo (F#)

Cronologia delle modifiche

Data

Cronologia

Motivo

Ottobre 2010

Chiarimento relativo al fatto che non tutte le operazioni asincrone si verificano in un thread in background.

Miglioramento delle informazioni.