Partager via


Expressions asynchrones

Cet article décrit la prise en charge de F# pour les expressions asynchrones. Les expressions asynchrones offrent un moyen d’effectuer des calculs de manière asynchrone, autrement dit, sans bloquer l’exécution d’autres travaux. Par exemple, les calculs asynchrones peuvent être utilisés pour écrire des applications qui ont des interfaces utilisateur qui restent réactives aux utilisateurs à mesure que l’application effectue d’autres tâches. Le modèle de programmation de workflows asynchrones F# permet d'écrire des programmes fonctionnels tout en cachant les détails de la transition des threads au sein d'une bibliothèque.

Le code asynchrone peut également être créé à l’aide de expressions de tâche, qui créent directement des tâches .NET. L’utilisation d’expressions de tâche est préférée lors de l’interopérabilité étendue avec les bibliothèques .NET qui créent ou consomment des tâches .NET. Lors de l’écriture de code asynchrone dans F#, les expressions asynchrones F# sont préférées, car elles sont plus succinctes, plus compositionnelles et évitent certaines mises en garde associées à des tâches .NET.

Syntaxe

async { expression }

Remarques

Dans la syntaxe précédente, le calcul représenté par expression est configuré pour s’exécuter de manière asynchrone, autrement dit, sans bloquer le thread de calcul actuel lorsque des opérations de veille asynchrones, des E/S et d’autres opérations asynchrones sont effectuées. Les calculs asynchrones sont souvent démarrés sur un thread d’arrière-plan pendant 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 lorsque 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 que vous souhaitez exécuter de manière asynchrone, puis à démarrer ces calculs à l’aide de l’une des fonctions de déclenchement. Le déclenchement que vous utilisez dépend de l’utilisation du thread actuel, d’un thread d’arrière-plan ou d’un objet de tâche .NET. Par exemple, pour démarrer un calcul asynchrone sur le thread actuel, vous pouvez utiliser Async.StartImmediate. Lorsque vous démarrez un calcul asynchrone à partir du thread d’interface utilisateur, vous ne bloquez pas la boucle d’événement principale qui traite les actions utilisateur telles que les séquences de touches et l’activité de la souris, afin que votre application reste réactive.

Liaison asynchrone à l'aide de let !

Dans une expression asynchrone, certaines expressions et opérations sont synchrones et certaines sont asynchrones. Lorsque vous appelez une méthode de façon asynchrone, au lieu d’une liaison let ordinaire, vous utilisez let!. L’effet de let! consiste à permettre à l’exécution de continuer sur d’autres calculs ou threads à mesure que le calcul est en cours d’exécution. Après le retour du côté droit de la liaison let!, 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 ultérieurement à l’aide, par exemple, Async.StartImmediate ou Async.RunSynchronously. La ligne de code qui utilise let! démarre le calcul et effectue une attente asynchrone : le thread est suspendu jusqu’à ce que le résultat soit disponible, à quel moment 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! ne peut être utilisé que pour attendre directement les calculs asynchrones F# Async<T>. Vous pouvez attendre d’autres types d’opérations asynchrones indirectement :

  • .NET, Task<TResult> et les tâches non génériques Task, en les combinant avec Async.AwaitTask
  • .NET, ValueTask<TResult> et la valeur non générique ValueTask, en les combinant avec .AsTask() et Async.AwaitTask
  • Tout objet suivant le modèle « GetAwaiter » spécifié dans F# RFC FS-1097, en combinant avec task { return! expr } |> Async.AwaitTask.

Flux de contrôle

Les expressions asynchrones peuvent inclure des constructions de flux de contrôle, telles que for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. elseet if .. then ... Elles 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 supportent pas les try .. finally .. asynchrones. Vous pouvez utiliser une expression de tâche pour ce cas.

Liaisonsuse et use!

Dans les expressions asynchrones, les liaisons use peuvent se lier à des valeurs de type IDisposable. Dans ce dernier cas, l'opération de nettoyage de l'élimination est exécutée de manière asynchrone.

Outre let!, vous pouvez utiliser use! pour effectuer des liaisons asynchrones. La différence entre let! et use! est la même que la différence entre let et use. Pour use!, l'objet est éliminé à la fermeture du champ d'application actuel. Notez que dans la version actuelle de F#, use! n’autorise pas l’initialisation d’une valeur à null, même si use le fait.

Primitives asynchrones

Une méthode qui effectue une tâche asynchrone unique et retourne le résultat est appelée primitive asynchrone, et celles-ci sont conçues spécifiquement pour une utilisation avec let!. Plusieurs primitives asynchrones sont définies dans la bibliothèque principale F#. Deux méthodes de ce type pour les applications web sont définies dans le module FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse et HttpClient.GetStringAsync (encapsulées avec Async.AwaitTask pour la compatibilité avec le modèle asynchrone de F#). 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 GetStringAsync produit 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 .NET Framework conçues pour d’autres modèles asynchrones avec le modèle de programmation asynchrone F#, vous créez une fonction qui retourne un objet F# Async. La bibliothèque F# a des fonctions qui facilitent cette opération.

Voici un exemple d’utilisation d’expressions asynchrones ; il y a beaucoup d’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. Lorsqu’une liaison est effectuée au résultat d’une primitive asynchrone, dans ce cas AsyncDownloadString, let! est utilisé au lieu 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 à l’aide de la fonction Async.Parallel avec la fonction Async.RunSynchronously. La fonction Async.Parallel prend une liste des objets Async, configure le code de chaque objet de tâche Async à exécuter en parallèle et retourne un objet Async qui représente le calcul parallèle. 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 terminées.

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

Voir aussi