githubModifier

Workflow asynchrone

Besoins

  1. Ne pas bloquer le thread courant en attendant un calcul long

  2. Permettre calculs en parallèle

  3. Indiquer qu'un calcul peut prendre du temps

Type Async<'T>

  • Représente un calcul asynchrone

  • Similaire au pattern async/await avant l'heure 📆

    • 2007 : Async<'T> F♯

    • 2012 : Task<T> .NET et pattern async/await

    • 2017 : Promise JavaScript et pattern async/await

Méthodes renvoyant un objet Async

Async.AwaitTask(task: Task or Task<'T>) : Async<'T>

  • Conversion d'une Task (.NET) en Async (F♯)

Async.Sleep(milliseconds or TimeSpan) : Async<unit>

  • await Task.Delay()Thread.Sleep → ne bloque pas le thread courant

FSharp.Control.CommonExtensionsarrow-up-right : étend le type System.IO.Stream

  • AsyncRead(buffer: byte[], ?offset: int, ?count: int) : Async<int>

  • AsyncWrite(buffer: byte[], ?offset: int, ?count: int) : Async<unit>

FSharp.Control.WebExtensionsarrow-up-right : étend le type System.Net.WebClient

  • AsyncDownloadData(address: Uri) : Async<byte[]>

  • AsyncDownloadString(address: Uri) : Async<string

Lancement d'un calcul async

Async.RunSynchronously(calc: Async<'T>, ?timeoutMs: int, ?cancellationToken) : 'T → Attend la fin du calcul mais bloque le thread appelant ! (≠ await C♯) ⚠️

Async.Start(operation: Async<unit>, ?cancellationToken) : unit → Exécute l'opération en background (sans bloqué le thread appelant) ⚠️ Si une exception survient, elle est "avalée" !

Async.StartImmediate(calc: Async<'T>, ?cancellationToken) : unit → Exécute le calcul dans le thread appelant ! 💡 Pratique dans une GUI pour la mettre à jour : barre de progression...

Async.StartWithContinuations(calc, continuations..., ?cancellationToken) → Idem Async.RunSynchronously ⚠️ ... avec 3 callbacks de continuation : en cas de succès ✅, d'exception 💥 et d'annulation 🛑

Bloc async { expression }

A.k.a. Async workflow

Syntaxe pour écrire de manière séquentielle un calcul asynchrone → Le résultat du calcul est wrappé dans un objet Async

Mots clés

  • return → valeur finale du calcul - unit si omis

  • let! (prononcer « let bang ») → accès au résultat d'un sous-calcul async (≃ await en C♯)

  • use! → idem use (gestion d'un IDisposable) + let!

  • do! → idem let! pour calcul async sans retour (Async<unit>)

Usage inapproprié de Async.RunSynchronously

Async.RunSynchronously lance le calcul et renvoie son résultat MAIS en bloquant le thread appelant ! Ne l'utiliser qu'en « bout de chaîne » et pas pour unwrap des calculs asynchrones intermédiaires ! Utiliser plutôt un bloc async.

Calculs en parallèle

Async.Parallel

Async.Parallel(computations: seq<Async<'T>>, ?maxBranches) : Async<'T[]>

Task.WhenAll : modèle Fork-Joinarrow-up-right

  • Fork : calculs lancés en parallèle

  • Attente de la terminaison de tous les calculs

  • Join : agrégation des résultats (qui sont du même type)

    • dans le même ordre que les calculs

Async.StartChild

Async.StartChild(calc: Async<'T>, ?timeoutMs: int) : Async<Async<'T>>

Permet de lancer en parallèle plusieurs calculs → ... dont les résultats sont de types différents (≠ Async.Parallel)

S'utilise dans bloc async avec 2 let! par calcul enfant (cf. Async<Async<'T>>)

Annulation conjointe 📍 Annulation d'une tâche → Le calcul enfant partage le jeton d’annulation du calcul parent

Exemple :

Soit le fonction delay → qui renvoie la valeur spécifiée x → au bout de ms millisecondes

💡 Minutage avec la directive FSI #time (docarrow-up-right)

Annulation d'une tâche

Se base sur un CancellationToken/Source par défaut ou explicite :

  • Async.RunSynchronously(computation, ?timeout, ?cancellationToken)

  • Async.Start(computation, ?cancellationToken)

Déclencher l'annulation

  • Token explicite + cancellationTokenSource.Cancel()

  • Token explicite avec timeout new CancellationTokenSource(timeout)

  • Token par défaut : Async.CancelDefaultToken()OperationCanceledException💥

Vérifier l'annulation

  • Implicite : à chaque mot clé dans bloc async : let, let!, for...

  • Explicite local : let! ct = Async.CancellationToken puis ct.IsCancellationRequested

  • Explicite global : Async.OnCancel(callback)

Exemple :

Résultat :

Mis à jour