首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 呼叫外部進程完整封裝:管道死鎖解決、錯誤報告抑制、非同步讀取

     編輯:關於C#

常見簡易寫法的三大坑

  1. 管道死鎖:子進程大量輸出 → 標準輸出管道緩衝區滿 → 子進程阻塞;父進程未及時讀取 → 雙方死鎖。
  2. 錯誤管道同樣死鎖:需同時讀取 StdOut 與 StdErr,單執行緒無法並行。
  3. Windows 錯誤報告彈窗:子進程 Crash 彈出「傳送錯誤報告」對話框,導致父進程 WaitForExit 永久等待。

完整解決方案架構

  • RunProcess:主類別,負責啟動、讀取 StdOut、協調錯誤讀取執行緒、抑制錯誤報告。
  • ReadErrorThread:專責執行緒非同步讀取 StdErr,避免死鎖。
  • Registry 抑制錯誤報告:修改 HKLM\Software\Microsoft\PCHealth\ErrorReporting\DoReport=0, ShowUI=0。

核心程式碼


public class RunProcess
{
    public string Output { get; private set; }
    public string Error { get; private set; }
    public bool HasError => !string.IsNullOrEmpty(Error);

    public void Run(string fileName, string arguments)
    {
        // 1. 抑制錯誤報告對話框
        SuppressErrorReporting();

        var outputSb = new StringBuilder();
        Error = Output = "";

        using (var proc = new Process())
        {
            proc.StartInfo.FileName = fileName;
            proc.StartInfo.Arguments = arguments;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.CreateNoWindow = true;
            proc.Start();

            // 2. 啟動錯誤讀取執行緒
            var errReader = new ReadErrorThread(proc);

            // 3. 主執行緒讀取標準輸出 (逐行避免大記憶體)
            while (!proc.HasExited)
                outputSb.AppendLine(proc.StandardOutput.ReadLine());
            outputSb.Append(proc.StandardOutput.ReadToEnd());

            // 4. 等待錯誤讀取完成
            while (!errReader.HasExisted) Thread.Sleep(1);
            Error = errReader.Error;

            Output = outputSb.ToString();
        }
    }

    private void SuppressErrorReporting()
    {
        try
        {
            using (var key = Registry.LocalMachine.OpenSubKey(
                @"Software\Microsoft\PCHealth\ErrorReporting", true))
            {
                if ((int)key.GetValue("DoReport") != 0) key.SetValue("DoReport", 0);
                if ((int)key.GetValue("ShowUI") != 0) key.SetValue("ShowUI", 0);
            }
        }
        catch { /* 忽略權限不足 */ }
    }
}

class ReadErrorThread
{
    public string Error { get; private set; }
    public bool HasExisted { get; private set; }
    private readonly Process _proc;

    public ReadErrorThread(Process proc) { _proc = proc; new Thread(Run).Start(); }

    private void Run()
    {
        var sb = new StringBuilder();
        while (!_proc.HasExited) sb.AppendLine(_proc.StandardError.ReadLine());
        sb.Append(_proc.StandardError.ReadToEnd());
        Error = sb.ToString();
        HasExisted = true;
    }
}

使用範例


var runner = new RunProcess();
runner.Run("myconsole.exe", "arg1 arg2");
if (runner.HasError) Log.Error(runner.Error);
else Console.WriteLine(runner.Output);

現代化替代:.NET Core 2.1+ Process API


// 內建非同步事件,無需手寫執行緒
var proc = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "cmd.exe",
        Arguments = "/c dir",
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
        CreateNoWindow = true
    },
    EnableRaisingEvents = true
};

proc.OutputDataReceived += (s, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
proc.ErrorDataReceived += (s, e) => { if (e.Data != null) Console.Error.WriteLine(e.Data); };

proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await proc.WaitForExitAsync(); // .NET 7+ 或自行 Task 包裝

小結

外部進程呼叫核心在於**非同步雙管道讀取**與**錯誤報告抑制**。.NET Framework 時代需手寫執行緒;.NET Core 內建事件模型大幅簡化程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved