2016-12-30 3 views
4

je le code suivant dans F # 4,0f # effectuer attendent méthodes asynchrones dans list.iteri

let processEscalation escalationAction (escalationEvents:UpdateCmd.Record list) = 
    printf "%A" Environment.NewLine 
    printf "Started %A" escalationAction 
    escalationEvents 
    |> List.iter (fun x -> 
     printf "%A" Environment.NewLine 
     printf "escalation %A for with action: %A" x.incident_id escalationAction 
     service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated")) 
     |> Async.AwaitTask 
     |> ignore) 


let ComposeEscalation() = 
    let escalationlevels = ["ESC1 REACHED"; "ESC2 REACHED"; "ESC3 REACHED"] 
    escalationlevels 
    |> List.map getEscalationEvents 
    |> List.iteri (fun i x -> processEscalation escalationlevels.[i] x) 

où la ligne suivante est un appel à une méthode C# async que qui renvoie la tâche

service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated")) 

La méthode d'escalade de composition appelle le processEscalation trois fois. Toutefois, le deuxième appel commence avant la fin du premier appel. Comment puis-je m'assurer que la dernière ligne list.iteri les attend et les traite séquentiellement? Peut-être que processEscalation devrait être dans une expression de calcul asynchrone?

+0

Pourquoi 'processEscalation' n'attend-il la tâche que pour l'ignorer? –

+0

'processEscalation' est utilisé pour effectuer une requête de service Web mais renvoie une tâche . Pour ce script particulier, je ne l'appelle que pour les effets secondaires, pas intéressé par la valeur retournée. Désolé, je suis relativement nouveau à la programmation fonctionnelle, donc je reconnais que ce n'est peut-être pas le meilleur moyen. – Chinwobble

Répondre

4

Qu'est-ce que Async.AwaitTask fait est qu'il renvoie un Async de calcul qui peut être utilisé pour attendre la fin de la tâche. Dans votre cas, vous ne faites rien avec, donc la boucle passe à l'itération suivante.

Vous voulez quelque chose comme ceci:

service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated")) 
|> Async.AwaitTask 
|> Async.RunSynchronously 
|> ignore 

Cela devrait avoir pour effet que vous attendez, mais certainement il y a plus agréables, des moyens plus composables d'exprimer une telle logique.

Modifier: Ce que je voulais dire quelque chose comme ça, en contrepartie de la fonction principale Async.Parallel:

module Async = 

    let sequential (asyncs: seq<Async<'t>>) : Async<'t []> = 
     let rec loop acc remaining = 
      async { 
       match remaining with 
       | [] -> return Array.ofList (List.rev acc) 
       | x::xs -> 
        let! res = x 
        return! loop (res::acc) xs 
      } 
     loop [] (List.ofSeq asyncs) 

Ensuite, vous pouvez faire quelque chose le long de ces lignes:

escalationEvents 
// a collection of asyncs - note that the task won't start until the async is ran 
|> List.map (fun x -> 
    async { 
     let task = 
      service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated")) 
     return! Async.AwaitTask task 
    }) 
// go from a collection of asyncs into an async of a collection 
|> Async.sequential 
// you don't care about the result, so ignore it 
|> Async.Ignore 
// now that you have your async, you need to run it in a way that makes sense 
// in your context - Async.Start could be another option. 
|> Async.RunSynchronously 

L'avantage ici est-ce que, au lieu de tout regrouper en une seule boucle, vous avez divisé le calcul en étapes bien délimitées. Il est facile de suivre et de refactoriser (par exemple, si vous avez besoin de traiter ces événements en parallèle, vous n'avez qu'à changer une étape dans le pipeline).

+0

Merci qui a fonctionné! Cette méthode bloque-t-elle les threads? Pouvez-vous suggérer un modèle pour le faire plus élégamment? L'utilisation d'un agent (mailboxProcessor) serait-elle plus appropriée? – Chinwobble

+2

'RunSynchronously' exécute un bloc' async' sur le thread courant, le bloquant efficacement dans votre cas (puisque vous voulez attendre la fin de la tâche). – scrwtp

+1

Le processeur de boîte aux lettres serait un surdéveloppement conceptuel, mais ce que je voulais suggérer fonctionnerait sur un principe similaire - une primitive qui boucle récursivement sur une séquence d'asynchrones et recueille les résultats, tout comme la boucle d'un processeur de boîte aux lettres s'appelle récursivement. – scrwtp