Expressions asynchrones

Cet article décrit la prise en charge en F# des expressions asynchrones. Les expressions asynchrones permettent d’effectuer des calculs de manière asynchrone, c’est-à-dire sans bloquer l’exécution d’autres travaux. Par exemple, vous pouvez utiliser les calculs asynchrones afin d’écrire des applications dont l’IU reste réactive pour les utilisateurs, pendant que l’application effectue d’autres travaux. Le modèle de programmation des flux de travail asynchrones F# vous permet d’écrire des programmes fonctionnels tout en masquant les détails de la transition de thread au sein d’une bibliothèque.

Vous pouvez également créer du code asynchrone à l’aide d’expressions de tâche, qui créent directement des tâches .NET. Il est préférable d’utiliser des expressions de tâche en cas d’interopérabilité avancée avec les bibliothèques .NET qui créent ou consomment des tâches .NET. Quand vous écrivez la plupart du code asynchrone en F#, il est préférable d’utiliser des expressions asynchrones F#, car elles sont plus succinctes, plus compositionnelles et permettent d’éviter certains avertissements associés aux tâches .NET.

Syntaxe

async { expression }

Notes

Dans la syntaxe précédente, le calcul représenté par expression est configuré pour s’exécuter de manière asynchrone, c’est-à-dire sans bloquer le thread de calcul actuel quand des opérations de veille asynchrones, des E/S et d’autres opérations asynchrones sont effectuées. Les calculs asynchrones démarrent souvent sur un thread d’arrière-plan, alors que l’exécution se poursuit sur le thread actuel. Le type de l’expression est Async<'T>, où 'T est le type retourné par l’expression quand le mot clé return est utilisé.

La classe Async fournit des méthodes qui prennent en charge plusieurs scénarios. L’approche générale consiste à créer des objets Async qui représentent le calcul ou les calculs à exécuter de manière asynchrone, puis à démarrer ces calculs à l’aide de l’une des fonctions de déclenchement. Le déclenchement employé varie selon que vous souhaitez utiliser le thread actuel, un thread d’arrière-plan ou un objet de tâche .NET. Par exemple, pour démarrer un calcul asynchrone sur le thread actuel, vous pouvez utiliser Async.StartImmediate. Quand vous démarrez un calcul asynchrone à partir du thread d’IU, vous ne bloquez pas la boucle d’événements principale qui traite les actions de l’utilisateur, par exemple les séquences de touches et l’activité de la souris. Ainsi, votre application reste réactive.

Liaison asynchrone à l’aide de let!

Dans une expression asynchrone, certaines expressions et opérations sont synchrones, et d’autres asynchrones. Quand vous appelez une méthode de manière asynchrone, à la place d’une liaison let ordinaire, vous utilisez let!. let! permet à d’autres calculs ou threads de s’exécuter parallèlement à l’exécution d’un calcul. Une fois que le côté droit de la liaison let! est retourné, le reste de l’expression asynchrone reprend son exécution.

Le code suivant montre la différence entre let et let!. La ligne de code qui utilise let crée simplement un calcul asynchrone en tant qu’objet que vous pouvez exécuter plus tard à l’aide de Async.StartImmediate ou Async.RunSynchronously, par exemple. La ligne de code qui utilise let! démarre le calcul et effectue une attente asynchrone : le thread est interrompu jusqu’à ce que le résultat soit disponible, moment où l’exécution se poursuit.

// 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! peut uniquement être utilisé pour attendre les calculs asynchrones Async<T> F# directement. Vous pouvez attendre indirectement d’autres genres d’opération asynchrones :

  • Tâches .NET, Task<TResult> et Task non générique, en combinaison avec Async.AwaitTask
  • Tâches de valeur .NET, ValueTask<TResult> et ValueTask non générique, en combinaison avec .AsTask() et Async.AwaitTask
  • Tout objet conforme au modèle "GetAwaiter" spécifié dans la RFC F# FS-1097, en combinaison avec task { return! expr } |> Async.AwaitTask.

Flux de contrôle

Les expressions asynchrones peuvent inclure des constructions de flux de contrôle, par exemple for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. else et if .. then ... Celles-ci peuvent à leur tour inclure d’autres constructions asynchrones, à l’exception des gestionnaires with et finally, qui s’exécutent de manière synchrone.

Les expressions asynchrones F# ne prennent pas en charge un try .. finally .. asynchrone. Vous pouvez utiliser une expression de tâche pour ce cas de figure.

Liaisons use et use!

Dans les expressions asynchrones, les liaisons use peuvent être liées à des valeurs de type IDisposable. Pour ce dernier, l’opération de nettoyage de suppression est exécutée de manière asynchrone.

En plus de let!, vous pouvez utiliser use! pour effectuer des liaisons asynchrones. La différence entre let! et use! est identique à la différence entre let et use. Pour use!, l’objet est supprimé à la fermeture de l’étendue actuelle. Notez que dans la version actuelle de F#, use! ne permet pas d’initialiser une valeur en tant que valeur nulle, même si use le permet.

Primitives asynchrones

Une méthode qui effectue une seule tâche asynchrone et retourne le résultat est appelée primitive asynchrone. Elle est spécifiquement conçue pour être utilisée avec let!. Plusieurs primitives asynchrones sont définies dans la bibliothèque principale F#. Deux de ces méthodes pour les applications web sont définies dans le module FSharp.Control.WebExtensions : WebRequest.AsyncGetResponse et WebClient.AsyncDownloadString. Les deux primitives téléchargent des données à partir d’une page web, en fonction d’une URL. AsyncGetResponse produit un objet System.Net.WebResponse, et AsyncDownloadString une chaîne qui représente le code HTML d’une page web.

Plusieurs primitives pour les opérations d’E/S asynchrones sont incluses dans le module FSharp.Control.CommonExtensions. Ces méthodes d’extension de la classe System.IO.Stream sont Stream.AsyncRead et Stream.AsyncWrite.

Vous pouvez également écrire vos propres primitives asynchrones en définissant une fonction ou une méthode dont le corps est une expression asynchrone.

Pour utiliser des méthodes asynchrones dans le .NET Framework, qui sont conçues pour d’autres modèles asynchrones avec le modèle de programmation asynchrone F#, créez une fonction qui retourne un objet Async F#. La bibliothèque F# comporte des fonctions qui facilitent cette tâche.

Un exemple d’utilisation d’expressions asynchrones est inclus ici. Il en existe de nombreux autres dans la documentation pour les méthodes de la classe Async.

Cet exemple montre comment utiliser des expressions asynchrones pour exécuter du code en parallèle.

Dans l’exemple de code suivant, une fonction fetchAsync obtient le texte HTML retourné à partir d’une requête web. La fonction fetchAsync contient un bloc de code asynchrone. Quand une liaison au résultat d’une primitive asynchrone est effectuée, dans le cas présent AsyncDownloadString, let! est utilisé à la place de let.

Vous utilisez la fonction Async.RunSynchronously pour exécuter une opération asynchrone et attendre son résultat. Par exemple, vous pouvez exécuter plusieurs opérations asynchrones en parallèle en utilisant la fonction Async.Parallel avec la fonction Async.RunSynchronously. La fonction Async.Parallel accepte une liste d’objets Async, configure le code de chaque objet de tâche Async pour qu’il s’exécute en parallèle, puis retourne un objet Async qui représente le calcul parallèle. Tout comme pour une seule opération, vous appelez Async.RunSynchronously pour démarrer l’exécution.

La fonction runAll lance trois expressions asynchrones en parallèle, et attend qu’elles soient toutes effectuées.

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

Voir aussi