2010-06-17 15 views
10

Estoy tratando de intentar iniciar un proceso desde F #, espere hasta que termine, pero también lea su salida progresivamente.Iniciando un proceso de forma sincrónica y "transmitiendo" la salida

¿Es esta la mejor/la mejor manera de hacerlo? (En mi caso estoy tratando de ejecutar comandos de Git, pero que es tangencial a la pregunta)

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 

Lo que no entiendo es cómo el proc.Start() puede devolver un valor lógico y también ser lo suficientemente asíncrono para que pueda obtener la salida progresivamente.

Por desgracia, no tienen actualmente un repositorio suficientemente grande - o bastante lenta máquina, para ser capaz de decir qué orden las cosas están sucediendo en ...

ACTUALIZACIÓN

Traté de Brian sugerencia, y funciona.

Mi pregunta era un poco vaga. Mi malentendido fue que asumí que Process.Start() devolvió el éxito del proceso como un todo, y no solo del 'Inicio', y por lo tanto no pude ver cómo podría funcionar.

+1

Tuve cierto éxito al utilizar flujos de trabajo asincrónicos para este tipo de tarea: http://stackoverflow.com/questions/2649161/need-help-regarding-async-and-fsi – Stringer

+0

No estoy seguro de cuál es la pregunta aquí. ¿Su código anterior funciona o no? ¿Cuál es el problema? –

+1

Me parece que podría escribir tester.exe, que escribe un par de líneas para stdout y flushes, espera dos segundos, escribe otras dos líneas, ... y la usa para probar si este código se comporta como usted desea, ¿sí? – Brian

Respuesta

12

El código que escribió en el formulario que escribió es (casi) correcto: proceso. Comience a iniciar el proceso que especifique en otro proceso para que las lecturas de flujo de salida se realicen en paralelo con la ejecución del proceso. Sin embargo, un problema es que debe lanzar una llamada para procesar. WaitForExit al final: el hecho de que la secuencia de salida esté cerrada no implica que el proceso haya finalizado.

Sin embargo, se encontrará con problemas de lectura sincronizada si intenta leer tanto stdout como stderr del proceso: no hay forma de leer 2 streams de forma síncrona y simultánea: se bloqueará si lee stdout y el proceso se escribe en stderr y espera que consuma su salida o viceversa.

Para mediar en esto, puede suscribirse a OutputDataRecieved y ErrorDataRecieved, así:

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() } 

También puede escribir algo en la línea de:

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

de F # -STYLE manejo de eventos reactiva .

+0

+1 para responder el resto de la pregunta que no pregunté :) Solo un error en su código es que Process.Start() no devuelve el proceso, sino que devuelve un valor booleano. – Benjol

+2

Llamo a Process.Start (ProcessStartInfo) estático que devuelve un proceso (http://msdn.microsoft.com/en-us/library/0w4h05yb.aspx) –

+1

el problema con esta solución es que los eventos 'OutputDataReceived' y 'ErrorDataReceived' solo se activa después de enviar un EOL, por lo que podría suceder que el proceso le pida al usuario que responda en la misma línea (por ejemplo,'¿Estás seguro? [S/N] ') y luego no se imprimirá con la envoltura del proceso F # – knocte

Cuestiones relacionadas