问题描述
我想编写运行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帮助的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!