Need help regarding Async and fsi

后端 未结 5 962
我寻月下人不归
我寻月下人不归 2020-12-17 05:24

I\'d like to write some code that runs a sequence of F# scripts (.fsx). The thing is that I could have literally hundreds of scripts and if I do that:

let sh         


        
5条回答
  •  长情又很酷
    2020-12-17 05:52

    I did some experiments and here is one way to deal with the problem discussed in the comments below my post and in the answer from Joel (which I think doesn't work currently, but could be fixed).

    I think the specification of Process is that it can trigger the Exited event after we set the EnableRaisingEvents property to true (and will trigger the event even if the process has already completed before we set the property). To handle this case correctly, we need to enable raising of events after we attach handler to the Exited event.

    This is a problme, because if we use AwaitEvent it will block the workflow until the event fires. We cannot do anything after calling AwaitEvent from the workflow (and if we set the property before calling AwaitEvent, then we get a race....). Vladimir's approach is correct, but I think there is a simpler way to deal with this.

    I'll create a function Event.guard taking an event and returning an event, which allows us to specify some function that will be executed after a handler is attached to the event. This means that if we do some operation (which in turn triggers the event) inside this function, the event will be handled.

    To use it for the problem discussed here, we need to change my original solution as follows. Firstly, the shellExecute function must not set the EnableRaisingEvents property (otherwise, we could lose the event!). Secondly, the waiting code should look like this:

    let rec loop scripts = async { 
      match scripts with 
      | [] -> printf "FINISHED"
      | script::scripts ->
        let p = shellExecute fsi script 
        let! exit = 
          p.Exited 
            |> Event.guard (fun () -> p.EnableRaisingEvents <- true)
            |> Async.AwaitEvent
        let output = p.StandardOutput.ReadToEnd()
        return! loop scripts  } 
    

    Note the use of the Event.guard function. Roughly, it says that after the workflow attaches handler to the p.Exited event, the provided lambda function will run (and will enable raising of events). However, we already attached the handler to the event, so if this causes the event immediately, we're fine!

    The implementation (for both Event and Observable) looks like this:

    module Event =
      let guard f (e:IEvent<'Del, 'Args>) = 
        let e = Event.map id e
        { new IEvent<'Args> with 
            member x.AddHandler(d) = e.AddHandler(d)
            member x.RemoveHandler(d) = e.RemoveHandler(d); f()
            member x.Subscribe(observer) = 
              let rm = e.Subscribe(observer) in f(); rm }
    
    module Observable =
      let guard f (e:IObservable<'Args>) = 
        { new IObservable<'Args> with 
            member x.Subscribe(observer) = 
              let rm = e.Subscribe(observer) in f(); rm }
    

    Nice thing is that this code is very straightforward.

提交回复
热议问题