Workflows asynchrones (F#)
Cette rubrique décrit la prise en charge en F# de l'exécution de calculs asynchrones, c'est-à-dire qui ne bloquent pas l'exécution d'autres tâches. Par exemple, les calculs asynchrones peuvent être utilisés pour écrire des applications dont les interfaces utilisateur restent réactives par rapport aux utilisateurs lorsque l'application exécute une autre tâche.
async { expression }
Notes
Dans la syntaxe précédente, le calcul représenté par expression est configuré afin d'être exécuté de façon asynchrone, c'est-à-dire sans bloquer le thread actuel de calcul lorsque des opérations asynchrones de veille, des E/S, et d'autres opérations asynchrones sont exécutées. Les calculs asynchrones sont souvent démarrés dans un thread d'arrière-plan pendant que l'opération se poursuit dans le thread actuel. Le type de l'expression est Async<'a>, où 'a est le type retourné par l'expression lorsque le mot clé return est utilisé. Le code d'une telle expression est appelé bloc asynchrone ou bloc async.
Il existe diverses façons de programmer de façon asynchrone, et 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 façon asynchrone, puis à démarrer ces calculs à l'aide de l'une des fonctions de déclenchement. Les différentes fonctions de déclenchement offrent différents moyens d'exécution pour les calculs asynchrones. Vous choisissez celle à utiliser selon que vous souhaitez utiliser le thread actuel, un thread d'arrière-plan ou un objet de tâche .NET Framework, et s'il existe ou non des fonctions de continuation qui doivent s'exécuter lorsque le calcul est terminé. 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énements principale qui traite les actions utilisateur telles que les séquences de touches et l'activité de la souris, donc votre application reste réactive.
Liaison asynchrone à l'aide de let!
Dans un workflow asynchrone, certaines expressions et opérations sont synchrones, alors que d'autres sont des calculs plus longs conçus pour retourner un résultat de façon asynchrone. Lorsque vous appelez une méthode de façon asynchrone, vous utilisez let! au lieu d'une liaison let standard. L'effet de let! est de permettre à l'exécution de se poursuivre sur d'autres calculs ou threads tandis que le calcul est exécuté. Une fois le côté droit de la liaison let! retourné, le reste du workflow asynchrone reprend l'exécution.
Le code suivant présente la différence entre let et let!. La ligne de code qui utilise let crée juste un calcul asynchrone en tant qu'objet que vous pouvez exécuter ultérieurement en utilisant, par exemple, Async.StartImmediate ou Async.RunSynchronously. La ligne de code qui utilise let! démarre le calcul, puis le thread est interrompu jusqu'à ce que le résultat soit disponible ; moment auquel l'exécution continue.
// 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)
En plus de 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 supprimé à la fin de la portée actuelle. Notez que, dans la version actuelle du langage F#, use! ne permet pas à une valeur d'être initialisée sur null, même si use le permet.
Primitives asynchrones
Une méthode qui effectue une tâche asynchrone unique et retourne le résultat est appelée primitive asynchrone. Ces méthodes sont conçues spécifiquement pour une utilisation 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 Microsoft.FSharp.Control.WebExtensions : WebRequest.AsyncGetResponse et WebClient.AsyncDownloadString. Les deux primitives téléchargent des données à partir d'une page Web, d'après une URL. AsyncGetResponse produit un objet WebResponse et AsyncDownloadString produit une chaîne qui représente le code HTML pour une page Web.
Plusieurs primitives pour les opérations d'E/S asynchrones sont incluses dans le module Microsoft.FSharp.Control.CommonExtensions. Ces méthodes d'extension de la classe Stream sont Stream.AsyncRead et Stream.AsyncWrite.
D'autres primitives asynchrones sont disponibles dans le F# PowerPack. Vous pouvez également écrire vos propres primitives asynchrones en définissant une fonction dont le corps complet est placé dans un bloc async.
Pour utiliser des méthodes asynchrones du .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 Async F#. La bibliothèque F# a des fonctions qui vous y aident.
Un exemple d'utilisation de workflows asynchrones est inclus ici ; il existe de nombreuses autres façons de procéder dans la documentation pour les méthodes de la classe Async.
Exemple
Cet exemple indique comment utiliser des flux de travail asynchrones pour exécuter des calculs 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 asynchrone de code. Lorsqu'une liaison est faite au résultat d'une primitive asynchrone, dans ce cas AsyncDownloadString, let! est utilisée au lieu de let.
Pour exécuter une opération asynchrone et attendre son résultat, vous utilisez la fonction Async.RunSynchronously. Par exemple, vous pouvez exécuter plusieurs opérations asynchrones en parallèle en utilisant la fonction Async.Parallel conjointement avec la fonction Async.RunSynchronously. La fonction Async.Parallel prend une liste des objets Async, configure le code pour chaque objet de tâche Async de manière à ce qu'ils s'exécutent en parallèle, puis retourne un objet Async qui représente le calcul parallèle. Comme pour une opération unique, vous appelez Async.RunSynchronously pour démarrer l'exécution.
La fonction runAll lance trois flux de travail asynchrones en parallèle et attend qu'ils se terminent.
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()