2011-12-21 88 views
2

我需要從C#應用程序運行外部進程,但在繼續執行之前等待它返回。同時,我需要從stdout獲取並更新包含進度信息的文本框。由於該命令可能會在此期間運行幾分鐘並打印信息,因此顯示該信息是絕對必要的;但多個命令可能會運行,並且必須按順序,所以等待也是如此。C#在讀取異步時等待shell命令返回

我嘗試使用:

p = Process.Start(); 
p.BeginOutputReadLine(); 
p.WaitForExit(); 

但凍結UI線程在等待和防止出現輸出。這:

p.Start(); 
p.BeginOutputReadLine(); 

while (!p.HasExited) 
{ 
    Application.DoEvents(); 
    Thread.Sleep(100); 
} 

工作更好,但是完全錯誤/一個壞主意,並沒有實際等待整個期間。

我也簡單地嘗試過使用BackgroundWorker來啓動shell進程,但我不確定如何讓UI線程等待而不會阻止工作者完成。

我想要做的是提供一個DialogResult ShellExecute(String cmd)類似於ShowDialog(),如果用戶允許該命令完成,單擊取消或命令的返回碼,則返回OK/Cancel(/ fail)結果。它不應該返回,直到命令完成(或取消)。

所有外殼命令與運行:

ProcessStartInfo info = new ProcessStartInfo 
{ 
    UseShellExecute = false, 
    CreateNoWindow = true, 
    RedirectStandardOutput = true, 
    RedirectStandardError = true, 

    FileName = "cmd.exe", 
    Arguments = "/c " + command 
}; 

重定向輸出和有效外殼執行該命令。

我該如何正確地創建一個啓動異步進程的函數,但仍然等待直到完成返回?

回答

1

重做了大部分代碼以使其正常工作。

ProgressForm類提供QueueCommand方法,取得所需的shell命令和前/後代理。顯示時,進度表使用後臺工作人員執行每個命令,處理返回碼並在適當的情況下執行下一個命令。

後臺工作程序等待每個shell命令完成(Process.WaitForExit()),同時異步提供UI線程輸出。完成後,它會調用啓用成功/ ok按鈕並隱藏進度欄的方法。

void m_Worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    int exec = 1; 
    while (CommandQueue.Count > 0) 
    { 
     if (e.Cancel) 
     { 
      e.Result = 1; 
      return; 
     } 

     WriteLine("Running command {0}, {1} remaining.", exec++, CommandQueue.Count); 
     StagedCommand command = CommandQueue.Peek(); 
     try 
     { 
      if (command.Pre != null) command.Pre(); 
      int result = ShellExec(command.Command); 
      if (command.Post != null) command.Post(); 
      CommandQueue.Dequeue(); 
      if (result != 0) 
      { 
       e.Result = result; 
       return; 
      } 
     } 
     catch (Exception exc) 
     { 
      WriteLine("Error: {0}", exc.Message); 
      e.Result = 1; 
      return; 
     } 
    } 

    WriteLine("All commands executed successfully."); 
    e.Result = 0; 
    return; 
} 

    int ShellExec(String command) 
    { 
     WriteLine(command); 
     Style = ProgressBarStyle.Marquee; 

     ProcessStartInfo info = new ProcessStartInfo 
     { 
      UseShellExecute = false, 
      LoadUserProfile = true, 
      ErrorDialog = false, 
      CreateNoWindow = true, 
      WindowStyle = ProcessWindowStyle.Hidden, 
      RedirectStandardOutput = true, 
      StandardOutputEncoding = Encoding.UTF8, 
      RedirectStandardError = true, 
      StandardErrorEncoding = Encoding.UTF8, 

      FileName = "cmd.exe", 
      Arguments = "/c " + command 
     }; 

     Process shell = new Process(); 
     shell.StartInfo = info; 
     shell.EnableRaisingEvents = true; 
     shell.ErrorDataReceived += new DataReceivedEventHandler(ShellErrorDataReceived); 
     shell.OutputDataReceived += new DataReceivedEventHandler(ShellOutputDataReceived); 

     shell.Start(); 
     shell.BeginErrorReadLine(); 
     shell.BeginOutputReadLine(); 
     shell.WaitForExit(); 

     return shell.ExitCode; 
    } 
2

從的ProcessStartInfo創建過程中,

澄清:SI的ProcessStartInfo需要像

si.CreateNoWindow=true; 
si.RedirectStandardOutput=true; 
si.RedirectStandardError=true; 
si.StandardOutputEncoding=Encoding.UTF8; 
si.StandardErrorEncoding=Encoding.UTF8; 
si.WindowStyle=ProcessWindowStyle.Hidden; 

然後前

p.Start(); 

使用

p.OutoutDataReceived+=OutputHandler; 

private static void OutputHandler(object theProcess, DataReceivedEventArgs evtdata) 
{ 
    //evtdata.Data has the output data 
    //use it, display it, or discard it 
} 
+0

顯然,如果你想捕捉標準錯誤的數據,你必須重複練習與p.ErrorDataReceievd + =的ErrorHandler – 2011-12-21 17:31:53

+0

這並不妨礙該函數創建並返回從啓動過程。我的輸出成功重定向,我更專注於等待無阻塞。 – ssube 2011-12-21 17:50:24