【发布时间】:2011-02-08 14:25:29
【问题描述】:
我想编写一些代码来运行一系列 F# 脚本 (.fsx)。问题是我可以拥有数百个脚本,如果我这样做:
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 脚本。 解决这个问题最惯用的方法是什么?
我还想弄清楚是否可以为每个正在执行的脚本检索返回码,如果不可行,请找到另一种方法。谢谢!
编辑:
非常感谢您的见解和链接!我学到了很多东西。
我只想按照 Tomas 的建议添加一些代码以使用 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:
所以我在让 Tomas 的保护代码工作时遇到了一些麻烦。我猜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 }
有趣的事情(正如 Tomas 所提到的)是,看起来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
}
请注意,我已将 fsi.exe 替换为 notepad.exe,因此我可以在调试器中逐步重播不同的场景,并自己明确控制进程的退出.
【问题讨论】:
-
您应该从这一行中删除 f():member this.RemoveHandler(d) = e.RemoveHandler(d); f() 否则 p.Start() 将在事件处理程序被删除时被调用。
标签: f# sequence asynchronous f#-interactive