Я хотел бы написать код, который запускает последовательность сценариев 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")
это может слишком сильно напрягать мою систему на 2 ГБ. В любом случае, я бы хотел запускать скрипты по пакетам из 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.
Какой самый идиоматичный способ решения этой проблемы?
Я также хотел бы выяснить, выполнимо ли получить код возврата для каждого исполняемого скрипта, и если нет, найти другой способ. Спасибо!
EDIT:
Большое спасибо за ваши идеи и ссылки! Я многому научился.
Я просто хочу добавить код для параллельного запуска пакетов, используя 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
}
Обратите внимание, что я заменил fsi.exe на notepad.exe , так что я могу пошагово воспроизводить различные сценарии в отладчике и самостоятельно управлять выходом из процесса.