2010-06-17 6 views
10

Je cherche à démarrer un processus à partir de F #, attendre jusqu'à la fin, mais aussi lire progressivement.Démarrer un processus de manière synchrone et "diffuser" la sortie

Est-ce la bonne/meilleure façon de le faire? (Dans mon cas, je suis en train d'exécuter des commandes git, mais qui est tangente à la question)

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 

Ce que je ne comprends pas comment le proc.Start() peut retourner une valeur booléenne et aussi être assez asynchrone pour que je puisse sortir progressivement de la sortie.

Malheureusement, je n'ai pas un dépôt assez grand - ou lent la machine assez, pour être en mesure de dire ce que les choses se passent dans l'ordre ...

MISE À JOUR

J'ai essayé de Brian suggestion, et cela fonctionne.

Ma question était un peu vague. Mon incompréhension était que je supposais que Process.Start() renvoyait le succès du processus dans son ensemble, et pas seulement du 'Start', et donc je ne pouvais pas voir comment cela fonctionnerait.

+1

J'ai eu un certain succès en utilisant Asynchronous Workflows pour ce genre de tâche: http://stackoverflow.com/questions/2649161/need-help-regarding-async-and-fsi – Stringer

+0

Je ne suis pas sûr de savoir quelle est la question ici. Est-ce que votre code ci-dessus fonctionne ou non? Quel est le problème? –

+1

Il me semble que vous pourriez écrire tester.exe, écrire quelques lignes sur stdout et vider, attendre deux secondes, écrire deux autres lignes, ... et l'utiliser pour tester si ce code se comporte comme vous voulez, oui? – Brian

Répondre

12

Le code que vous avez écrit dans le formulaire que vous avez écrit est (presque) ok: process.Start lance le processus que vous spécifiez dans, bien, un autre processus, afin que vos flux de sortie se déroulent parallèlement à votre exécution de processus. Un problème cependant est que vous devriez lancer un appel à process.WaitForExit à la fin - le fait que le flux de sortie est fermé n'implique pas que ce processus soit terminé. Cependant, vous rencontrerez des problèmes de lecture synchronisée si vous essayez de lire à la fois stdout et stderr du processus: il n'y a aucun moyen de lire deux flux de manière synchrone et simultanée - vous serez bloqué si vous lisez stdout et que le processus écrit stderr et attend que vous consommiez sa sortie ou vice versa.

Pour la médiation, vous pouvez vous abonner à OutputDataRecieved et ErrorDataRecieved, comme ceci:

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

Vous pouvez aussi écrire quelque chose le long des lignes de:

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

pour F # -STYLE gestion des événements réactifs .

+0

+1 pour répondre au reste de la question que je n'ai pas posée :) Une seule erreur dans votre code est que Process.Start() ne retourne pas le processus, il renvoie un booléen. – Benjol

+2

J'appelle un Process.Start statique (ProcessStartInfo) qui renvoie un processus (http://msdn.microsoft.com/en-us/library/0w4h05yb.aspx) –

+1

le problème avec cette solution est que les événements 'OutputDataReceived' et 'ErrorDataReceived' ne se déclenche qu'après l'envoi d'un EOL, il peut donc arriver que le processus demande à l'utilisateur de répondre à la même ligne (par exempleÊtes-vous sûr? [O/N] ') et il ne sera pas imprimé par le wrapper de processus F # – knocte

Questions connexes