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