本文介绍了需要有关异步和FSI帮助的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写运行F#脚本(.fsx)的序列一些code。事情是,我能有数以百计的脚本,如果我这样做:

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 shellExecute program args =
    let startInfo = new ProcessStartInfo()
    do startInfo.FileName        <- program
    do startInfo.Arguments       <- args
    do startInfo.UseShellExecute <- true
    do startInfo.WindowStyle     <- ProcessWindowStyle.Hidden

    //do printfn "%s" startInfo.Arguments
    let proc = Process.Start(startInfo)
    ()

scripts
|> Seq.iter (shellExecute "fsi")

它的可能的压力太大我2GB的系统。无论如何,我想按批次的n,这也似乎是一个很好的锻炼学习,才能运行脚本异步(我想这是要走的路)。

it could stress too much my 2GB system. Anyway, I'd like to run scripts by batch of n, which seems also a good exercise for learning Async (I guess it's the way to go).

我已经开始写一些code为该但不幸的是它不工作:

I have started to write some code for that but unfortunately it doesn't work:

open System.Diagnostics

let p = shellExecute "fsi" @"C:\Users\Stringer\foo.fsx"

async {
    let! exit = Async.AwaitEvent p.Exited
    do printfn "process has exited"
}
|> Async.StartImmediate

foo.fsx只是一个Hello World脚本。
什么将是解决这一问题的最惯用的方法是什么?

foo.fsx is just a hello world script.What would be the most idiomatic way of solving this problem?

我想也弄清楚它是否可行检索返回code语言为每个执行的脚本,如果没有,找到另一种方式。谢谢!

I'd like also to figure out if it's doable to retrieve a return code for each executing script and if not, find another way. Thanks!

编辑:

非常感谢您的见解和链接!我学到了很多东西。
我只想补充一些code使用并行运行batchs Async.Parallel 作为托马斯建议吧。请评论,如果有对我的切更好地实施功能。

Thanks a lot for your insights and links! I've learned a lot.I just want to add some code for running batchs in parallel using Async.Parallel as Tomas suggested it. Please comment if there is a better implementation for my cut function.

module Seq =
  /// Returns a sequence of sequences of N elements from the source sequence.
  /// If the length of the source sequence is not a multiple
  /// of N, last element of the returned sequence will have a length
  /// included between 1 and N-1.
  let cut (count : int) (source : seq<´T>) =
    let rec aux s length = seq {
      if (length < count) then yield s
      else
        yield Seq.take count s
        if (length <> count) then
          yield! aux (Seq.skip count s) (length - count)
      }
    aux source (Seq.length source)

let batchCount = 2
let filesPerBatch =
  let q = (scripts.Length / batchCount)
  q + if scripts.Length % batchCount = 0 then 0 else 1

let batchs =
  scripts
  |> Seq.cut filesPerBatch
  |> Seq.map Seq.toList
  |> Seq.map loop

Async.RunSynchronously (Async.Parallel batchs) |> ignore

EDIT2:

所以我不得不一些麻烦让托马斯的后卫code工作。我猜˚F函数曾在的AddHandler 方法被调用,否则,我们失去永远的事件......这里的在code:

So I had some troubles to get Tomas's guard code working. I guess the f function had to be called in AddHandler method, otherwise we loose the event for ever... Here's the code:

module Event =
  let guard f (e:IEvent<´Del, ´Args>) =
    let e = Event.map id e
    { new IEvent<´Args> with
        member this.AddHandler(d) = e.AddHandler(d); f() //must call f here!
        member this.RemoveHandler(d) = e.RemoveHandler(d); f()
        member this.Subscribe(observer) =
          let rm = e.Subscribe(observer) in f(); rm }

(由托马斯提及),有趣的是,它看起来像已退出事件进程终止时,即使这个过程还没有开始与 EnableRaisingEvents 设置为true。
当此属性最后设置为true,该事件被解雇了。

The interesting thing (as mentioned by Tomas) is that it looks like the Exited event is stored somewhere when the process terminates, even though the process has not started with EnableRaisingEvents set to true.When this property is finally set to true, the event is fired up.

由于我不知道这是官方规范(也有点偏执),我发现了另一个解决方案,包括在启动过程中的后卫功能,这样既保证了code将在任何情况下工作:

Since I'm not sure that this is the official specification (and also a bit paranoid), I found another solution that consists in starting the process in the guard function, so we ensure that the code will work on whichever situation:

let createStartInfo program args =
  new ProcessStartInfo
    (FileName = program, Arguments = args, UseShellExecute = false,
     WindowStyle = ProcessWindowStyle.Normal,
     RedirectStandardOutput = true)

let createProcess info =
  let p = new Process()
  do p.StartInfo           <- info
  do p.EnableRaisingEvents <- true
  p

let rec loop scripts = async {
  match scripts with
  | [] -> printfn "FINISHED"
  | script::scripts ->
    let args = sprintf "\"%s\"" script
    let p = createStartInfo "notepad" args |> createProcess
    let! exit =
      p.Exited
      |> Event.guard (fun () -> p.Start() |> ignore)
      |> Async.AwaitEvent
    let output = p.StandardOutput.ReadToEnd()
    do printfn "\nPROCESSED: %s, CODE: %d, OUTPUT: %A"script p.ExitCode output
    return! loop scripts
  }

注意我已经更换了的 fsi.exe 的通过的的notepad.exe 的,所以我可以重播不同的场景一步在调试器中的步骤和控制明确的过程我自己的出口

Notice I've replaced fsi.exe by notepad.exe so I can replay different scenarios step by step in the debugger and control explicitly the exit of the process myself.

推荐答案

我做了一些实验,这里是对付在我下面的文章的评论和乔尔(答案中讨论的问题的一个方法,我认为没有按'目前ŧ工作,但可能是固定的)。

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

我的认为的的过程的规范,它可以触发已退出事件之后,我们设置了 EnableRaisingEvents 属性真正(将触发事件,即使该过程已经完成了我们设定前该属性)。要正确处理这种情况下,我们需要的后,使事件的提高的我们重视处理程序的已退出事件。

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.

这是一个problme,因为如果我们使用 AwaitEvent 它会阻止工作流程,直到事件触发。调用 AwaitEvent 从工作流(如果我们调用 AwaitEvent 之前设置该属性后,我们不能做任何事情,然后我们得到一场比赛....)。 是正确的,但我认为这是一个简单的处理这种方式。

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.

我将创建一个函数 Event.guard 以一个事件,并返回一个事件,它允许我们指定将后部分功能青霉>一个处理器连接于该事件。这意味着,如果我们做一些操作(这反过来又触发事件)这个函数内,该事件将被处理。

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.

要使用它为这里讨论的问题,我们需要改变我原来的解决方案如下。首先,的ShellExecute 函数一定不能设置 EnableRaisingEvents 属性(否则,我们可能会失去的事件!)。其次,等待code应该是这样的:

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  }

请注意使用 Event.guard 的功能。粗略地说,它说,工作流处理器重视对 p.Exited后时,所提供的lambda函数将运行(并能使事件的认识)。但是,我们已经连接的处理程序事件,因此,如果这立即导致事件,我们很好!

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 }

好处是,这个code是非常简单的。

Nice thing is that this code is very straightforward.

这篇关于需要有关异步和FSI帮助的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 16:06