Синхронный запуск процесса и потоковая передача - PullRequest
11 голосов
/ 18 июня 2010

Я пытаюсь запустить процесс из F #, подождать, пока он не закончится, но также постепенно читать его вывод.

Это правильный / лучший способ сделать это? (В моем случае я пытаюсь выполнить команды git, но это касается вопроса)

let gitexecute (logger:string->unit) cmd = 
    let procStartInfo = new ProcessStartInfo(@"C:\Program Files\Git\bin\git.exe", cmd) 

    // Redirect to the Process.StandardOutput StreamReader.
    procStartInfo.RedirectStandardOutput <- true
    procStartInfo.UseShellExecute <- false;

    // Do not create the black window.
    procStartInfo.CreateNoWindow <- true;

    // Create a process, assign its ProcessStartInfo and start it
    let proc = new Process();
    proc.StartInfo <- procStartInfo;
    proc.Start() |> ignore

    // Get the output into a string
    while not proc.StandardOutput.EndOfStream do
        proc.StandardOutput.ReadLine() |> logger

Что я не понимаю, так это как proc.Start ()может возвращать логическое значение, а также быть достаточно асинхронным, чтобы я мог постепенно выводить результаты.

К сожалению, в настоящее время у меня нет достаточно большого хранилища - или достаточно медленной машины, чтобы я мог сказатьв каком порядке происходят вещи ...

ОБНОВЛЕНИЕ

Я попробовал предложение Брайана, и оно работает.

Мой вопрос был немного расплывчатым,Мое недоразумение заключалось в том, что я предполагал, что Process.Start () вернул успех процесса в целом, а не только «Пуск», и поэтому я не мог понять, как он может работать.

1 Ответ

14 голосов
/ 18 июня 2010

Код, который вы написали в форме, которую вы написали, является (почти) нормальным: process.Start Запустите процесс, который вы укажете, ну, в общем, другой процесс, так что чтение вашего выходного потока будет происходить параллельно с выполнением вашего процесса.Одна проблема, однако, заключается в том, что вы должны добавить вызов process.WaitForExit в конце концов - тот факт, что выходной поток закрыт, не означает, что процесс завершен.

Однако у вас возникнут проблемы с синхронным чтением, если выпопробуйте прочитать как stdout, так и stderr процесса: нет способа прочитать 2 потока синхронно и одновременно - вы зашли в тупик, если вы читаете stdout, а процесс записывает в stderr и ждет, пока вы используете его вывод, или наоборот.

Чтобы обеспечить это, вы можете подписаться на OutputDataReceded и ErrorDataRectained, например:

type ProcessResult = { exitCode : int; stdout : string; stderr : string }

let executeProcess (exe,cmdline) =
    let psi = new System.Diagnostics.ProcessStartInfo(exe,cmdline) 
    psi.UseShellExecute <- false
    psi.RedirectStandardOutput <- true
    psi.RedirectStandardError <- true
    psi.CreateNoWindow <- true        
    let p = System.Diagnostics.Process.Start(psi) 
    let output = new System.Text.StringBuilder()
    let error = new System.Text.StringBuilder()
    p.OutputDataReceived.Add(fun args -> output.Append(args.Data) |> ignore)
    p.ErrorDataReceived.Add(fun args -> error.Append(args.Data) |> ignore)
    p.BeginErrorReadLine()
    p.BeginOutputReadLine()
    p.WaitForExit()
    { exitCode = p.ExitCode; stdout = output.ToString(); stderr = error.ToString() }

Вы также можете написать что-то вроде:

async {
    while true do
        let! args = Async.AwaitEvent p.OutputDataReceived
        ...
} |> Async.StartImmediate

дляОбработка реактивных событий в стиле F #.

...