2016-12-11 60 views
2

.NET Framework v3.5發生以下問題。不知道它是否適用於v4 *。什麼是過早打破我的Process.StartInfo.OutputDataReceived回調?

要捕獲的stdout一段過程我已經成功地使用p.StartInfo.UseShellExecute = false;p.StartInfo.RedirectStandardOutput = true;,並掛接到p.StartInfo.OutputDataReceived+=...;事件處理程序。然後我打電話p.Start();,然後p.BeginOutputReadLine();,然後p.WaitForExit();

一切都很好。如預期的那樣,我逐行獲取事件處理函數的所有stdout。

我不得不引入一個超時而不是WaitForExit(),因爲有些進程不可預知地觸發標準輸入的請求(例如,你確定?[y/n])導致死鎖,我永遠等待,他們也是如此。

我嘗試的第一件事是更改爲while (!p.HasExited && DateTime.Now < timeoutmom) p.WaitForExit(200);,其中timeoutmomentproc.Start()後2分鐘。這是我遇到問題的時候。非常一致的是,代碼適用於產生高達幾百行標準輸出的調用,但是對於一次調用產生大約7500行的調用會中斷。會發生什麼事是proc.WaitForExit(200);線程退出while當我OutputDataReceived事件處理程序只要求〜7300行(這個數字又是非常一致的它僅+/- 1測試之間變化)和處理程序未對其餘叫了標準線,所以我失去了他們。

奇怪的是,如果我避免 WaitForExit(200)而不是使用 while (!p.HasExited && DateTime.Now < timeoutmom) System.Threading.Thread.Sleep(1000);(未在下面的代碼中顯示),問題不會出現。 當我發佈這個問題時,我確信使用Sleep(1000)避免了這個問題,但我錯了。它的工作數十次,然後它沒有,它開始表現就像當我檢查WaitForExit(200)

我現在推測,此問題的原因是:(1)我花很長時間來處理每個OutputDataReceived回調。我注意到,當我在事件處理程序中添加了一個條件斷點時,問題變得更加嚴重,這使得方法執行時間延長了很多。我現在可以通過簡單地添加3x Debug.WriteLines而無需條件斷點來重現該問題; PLUS(2)在系統有機會對我的事件處理程序執行所有回調之前,我訪問HasExited/WaitForExit(200)時,我的上下文受到某種程度的破壞。我現在在p.Start()之後以及在訪問任何p.*方法之前做了一個盲目的System.Threading.Thread.Sleep(30000),我得到了所有的回調。當我使用WaitForExit()時,似乎我可以花費很多時間來處理每個回調,而且我仍然可以完成所有回調。

有人可以使更多的這種意義?

代碼:

private int _execOsProc(
     ProcessStartInfo Psi 
     , string SecInsensArgs 
     , TextWriter ExtraStdOutAndErrTgt 
     , bool OutputToExtraStdOutOnly 
     ) 
    { 
     var pr = new Process(); 
     pr.StartInfo = Psi; 
     pr.StartInfo.UseShellExecute = false; 
     pr.StartInfo.RedirectStandardOutput = pr.StartInfo.RedirectStandardError = true; 
     pr.StartInfo.CreateNoWindow = true; 
     var ol = new DataReceivedEventHandler(this._stdOutDataReceived); 
     var el = new DataReceivedEventHandler(this._stdErrDataReceived); 
     pr.OutputDataReceived += ol; 
     pr.ErrorDataReceived += el; 
     try 
     { 
      __logger.Debug("Executing: \"" + pr.StartInfo.FileName + "\" " + SecInsensArgs); 
      if (ExtraStdOutAndErrTgt == null) 
      { 
       this.__outputToExtraStdOutOnly = false; 
      } 
      else 
      { 
       this.__extraStdOutAndErrTgt = ExtraStdOutAndErrTgt; 
       this.__outputToExtraStdOutOnly = OutputToExtraStdOutOnly; 
      } 
      pr.Start(); 
      pr.BeginOutputReadLine(); 
      pr.BeginErrorReadLine(); 
      var startmom = DateTime.Now; 
      var timeoutmom = startmom.AddMinutes(2); 
      while (!pr.HasExited && DateTime.Now < timeoutmom) pr.WaitForExit(200); 
      pr.CancelOutputRead(); 
      pr.CancelErrorRead(); 
      if (pr.HasExited) 
      { 
       __logger.Debug("Execution finished with exit status code: " + pr.ExitCode); 
       return pr.ExitCode; 
      } 
      else 
      { 
       __logger.Debug("Timeout while waiting for execution to finish"); 
       pr.Kill(); 
       return -100; 
      } 
     } 
     finally 
     { 
      pr.OutputDataReceived -= ol; 
      pr.ErrorDataReceived -= el; 
      if (this.__extraStdOutAndErrTgt != null) 
      { 
       this.__extraStdOutAndErrTgt = null; 
       this.__outputToExtraStdOutOnly = false; 
      } 
     } 
    } 

    private void _stdOutDataReceived(
     object sender 
     , DataReceivedEventArgs e 
     ) 
    { 
     string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim(); 
     if (!this.__outputToExtraStdOutOnly) __logger.Debug("SO: " + rdata); 
     if (this.__extraStdOutAndErrTgt != null) 
     { 
      lock (this.__extraStdOutAndErrTgt) 
      { 
       try 
       { 
        this.__extraStdOutAndErrTgt.WriteLine(rdata); 
        this.__extraStdOutAndErrTgt.Flush(); 
       } 
       catch (Exception exc) 
       { 
        __logger.Warn(
           "WARNING: Error detected but ignored during extra stream write" 
            + " on SODR. Details: " + exc.Message 
           , exc 
           ); 
       } 
      } 
     } 
    } 

    private void _stdErrDataReceived(
     object sender 
     , DataReceivedEventArgs e 
     ) 
    { 
     string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim(); 
     if (!__outputToExtraStdOutOnly) __logger.Debug("SE: " + rdata); 
     if (this.__extraStdOutAndErrTgt != null) 
     { 
      lock (this.__extraStdOutAndErrTgt) 
      { 
       try 
       { 
        this.__extraStdOutAndErrTgt.WriteLine(rdata); 
        this.__extraStdOutAndErrTgt.Flush(); 
       } 
       catch (Exception exc) 
       { 
        __logger.Warn(
           "WARNING: Error detected but ignored during extra stream write" 
            + " on SEDR. Details: " + exc.Message 
           , exc 
           ); 
       } 
      } 
     } 
    } 

回答

0

我不知道這是否會解決這個問題,但它是太長,將它張貼在註釋中。

MSDN說,大約Process.HasExited

在標準輸出已被重定向到異步事件 處理程序,它有可能輸出處理不會有 完成時,此屬性將返回true。爲了確保異步 事件處理完畢後,調用WaitForExit()重載 不帶任何參數檢查HasExited之前。

和約WaitForExit()

此重載確保所有處理已經完成, 包括用於重定向標準 輸出的異步事件的處理。當標準輸出已被重定向到 異步事件處理程序的 WaitForExit(Int32)已超載的呼叫後,您應該使用此重載。

這表明,對WaitForExit()調用沒有參數應該解決問題。喜歡的東西:

var startmom = DateTime.Now; 
var timeoutmom = startmom.AddMinutes(2); 
while (!pr.HasExited && DateTime.Now < timeoutmom) 
    pr.WaitForExit(200); 

if (pr.HasExited) 
{ 
    WaitForExit();//Ensure that redirected output buffers are flushed 

    pr.CancelOutputRead(); 
    pr.CancelErrorRead(); 

    __logger.Debug("Execution finished with exit status code: " + pr.ExitCode); 
    return pr.ExitCode; 
} 
else 
{ 
    pr.CancelOutputRead(); 
    pr.CancelErrorRead(); 

    __logger.Debug("Timeout while waiting for execution to finish"); 

    pr.Kill(); 
    return -100; 
} 
+0

耶穌!我不能f ***相信這一點...我遵循v3.5的文檔,其中此信息尚未被回溯。它完全符合我的問題的描述。我會嘗試一下並回複評論。最大的問題似乎是如果最終的WaitForExit()確實在HasExited變爲true之後刷新緩衝區。十分感謝你! – bogdan

+0

.NET 3.5中可能的行爲是不同的,WaitForExit()調用未neccessary - 爲「WaitForExit(-1)」的例子行爲已更改後過.NET 3.5。不幸的是,MSDN沒有明確描述HasExited == true後WaitForExit()的行爲,所以你只能希望它能起作用。 –

相關問題