我想写运行F#脚本(.fsx)的序列一些代码。事情是,我能有数以百计的脚本,如果我这样做:需要帮助有关异步和FSI
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个,这也似乎是一个很好的锻炼学习Async
(我想这是要走的路)运行的脚本。
我已经开始写一些代码,但遗憾的是它不工作:
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脚本。 解决这个问题最常用的方式是什么?
我想也弄清楚它是否是可行的检索返回代码为每个执行的脚本,如果没有,找到另一种方式。谢谢!
编辑:
非常感谢您的见解和链接!我学到了很多。 我只是想添加一些代码使用Async.Parallel
作为托马斯建议它运行在平行批次的。请评论我的cut
函数是否有更好的实现。
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:
所以我有一些麻烦让托马斯的后卫代码工作。我猜f
功能曾在AddHandler
方法被调用,否则我们失去永远的事件......下面的代码:
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 }
(由托马斯提到),有趣的是,它看起来像Exited
在进程终止时,即使该过程还没有开始EnableRaisingEvents
设置为true事件被保存在某个地方。 当此属性最终设置为true时,事件将启动。
因为我不知道,这是官方的规格(也有点偏执),我发现了另一个解决方案,包括在开始在guard
功能的过程中,所以我们确保代码将在任何情况下工作:
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
}
通知我已经NOTEPAD.EXE更换fsi.exe这样我就可以重放不同的场景一步在调试器中的步骤和控制明确的处理自己的出口。
您应该删除f()的从该行: 构件this.RemoveHandler(d)= e.RemoveHandler(d); f() 否则当事件处理程序被删除时,p.Start()将被调用。 – Oenotria 2012-02-26 06:18:49