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

C# 實現 Windows 匿名管道:重定向子進程輸出到 UI 界面

     編輯:關於C語言

前言:管道技術在 .NET 中的簡化實現

傳統 Windows 管道編程需調用 Win32 API (`CreatePipe`, `CreateProcess` 等),繁瑣且易出錯。.NET `System.Diagnostics.Process` 類封裝了標準輸入/輸出/錯誤重定向,配合異步讀取線程,輕鬆實現「啟動子進程 → 捕獲輸出 → 實時顯示在 UI」的管道通信模式。本文演示核心實現模式。

核心實現步驟

1. 配置 ProcessStartInfo 啟用重定向


var proc = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = scriptPath,           // 可執行文件/腳本路徑
        WorkingDirectory = workDir,      // 工作目錄
        CreateNoWindow = true,           // 不創建控制台窗口
        UseShellExecute = false,         // 必須 false 才能重定向
        RedirectStandardOutput = true,   // 重定向標準輸出
        RedirectStandardError = true,    // 可選:重定向標準錯誤
        // StandardOutputEncoding = Encoding.UTF8, // 指定編碼避免亂碼
    }
};

2. 異步讀取輸出線程 (避免死鎖與 UI 凍結)


// 同步事件通知讀取完成
var outputEvent = new AutoResetEvent(false);
var outputThread = new Thread(() => ReadOutput(proc, outputEvent));
outputThread.Start();

// 主線程可繼續做其他事,或 WaitHandle.WaitAll 等待
// WaitHandle.WaitAll(new[] { outputEvent }); // 若需同步等待

3. 讀取循環核心邏輯


private void ReadOutput(Process process, AutoResetEvent completedEvent)
{
    try
    {
        // 使用 StreamReader 逐行讀取 (內部有緩衝,比 ReadLine 高效)
        using (var reader = process.StandardOutput)
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                // 跨線程更新 UI 必須 Invoke
                txtOutput.Invoke((Action)(() =>
                {
                    // AppendText 比 += 避免閃爍,自動滾動
                    txtOutput.AppendText(line + Environment.NewLine);
                    txtOutput.SelectionStart = txtOutput.Text.Length;
                    txtOutput.ScrollToCaret();
                }));
            }
        }
    }
    catch (Exception ex)
    {
        // 記錄異常
    }
    finally
    {
        completedEvent.Set(); // 通知主線程讀取結束
    }
}

4. 啟動進程並等待 (完整流程)


proc.Start();

// 可選:寫入標準輸入 (若 RedirectStandardInput=true)
// proc.StandardInput.WriteLine("input data");
// proc.StandardInput.Close();

// 等待進程退出 (異步讀取線程同時運行)
proc.WaitForExit();

// 確保讀取線程結束
outputEvent.WaitOne(); 
outputThread.Join(TimeSpan.FromSeconds(1));

關鍵技術點深度解析

為什麼必須 `UseShellExecute = false`?

Shell 執行模式 (`true`) 由系統 Shell (explorer) 啟動進程,不支持標準流重定向。`false` 直接由 CLR 創建進程並建立匿名管道連接 `stdin/stdout/stderr`。

死鎖風險與解決

  • 風險:同步 `ReadToEnd()` 或 `WaitForExit()` 未讀取輸出時,子進程輸出緩衝區填滿阻塞,主進程等待退出 → 死鎖
  • 解決:異步讀取 (獨立線程/Task) + `WaitForExit()`,或使用 `BeginOutputReadLine` 事件異步模式

`AppendText` vs `+=`


// 差:每次重新分配字符串、重繪整個控件 → 閃爍、性能差
txtOutput.Text += line + "\r\n"; 

// 優:原生追加、僅重繪新增部分、自動滾動支持
txtOutput.AppendText(line + "\r\n");
txtOutput.ScrollToCaret();

編碼處理

子進程輸出編碼可能與當前系統 `Encoding.Default` 不一致。建議顯式指定:


StartInfo.StandardOutputEncoding = Encoding.UTF8; // 或 Encoding.GetEncoding("GBK")
StartInfo.StandardErrorEncoding = Encoding.UTF8;

現代化異步寫法 (.NET 4.5+ / C# 5.0+)


public async Task RunProcessAsync(string fileName, string args, IProgress outputProgress)
{
    var tcs = new TaskCompletionSource();
    
    var proc = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = fileName,
            Arguments = args,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8
        },
        EnableRaisingEvents = true // 允許 Exited 事件
    };

    proc.OutputDataReceived += (sender, e) => 
    {
        if (e.Data != null) outputProgress.Report(e.Data);
        else tcs.TrySetResult(proc.ExitCode); // null 表示流關閉
    };
    proc.ErrorDataReceived += (sender, e) => 
    {
        if (e.Data != null) outputProgress.Report($"[ERR] {e.Data}");
    };

    proc.Start();
    proc.BeginOutputReadLine(); // 異步事件讀取
    proc.BeginErrorReadLine();
    
    await proc.WaitForExitAsync(); // 需擴展方法或 Task.Run(() => proc.WaitForExit())
    return proc.ExitCode;
}

// UI 調用
await RunProcessAsync("cmd.exe", "/c dir", new Progress(line => 
    txtOutput.AppendText(line + Environment.NewLine)));

總結

.NET 通過 `Process` 類將複雜的管道創建、進程啟動、流重定向封裝為簡潔 API。核心模式:**`UseShellExecute=false` + `RedirectStandardOutput=true` + 異步讀取線程/事件 + UI `Invoke` 更新**。掌握此模式,可輕鬆實現控制台程序封裝、腳本執行器、構建系統輸出捕獲、自動化測試運行器等場景。現代代碼請優先使用 `async/await` + `OutputDataReceived` 事件模式,代碼更清晰、異常處理更完善。

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