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

WinForms 跨執行緒操作 UI 完整指南:BackgroundWorker vs Thread/Invoke

     編輯:關於C#

WinForms 單一 UI 執行緒模型要求:只有建立控制項的執行緒能存取它。耗時工作必須移至背景執行緒,完成後再安全回傳 UI。本文對比兩種主流模式的實作細節與避坑指南。

模式一:BackgroundWorker — 事件驅動、內建跨執行緒封送

BackgroundWorker 封裝執行緒池,提供三大事件自動處理上下文切換:

  • DoWork:背景執行緒,**不可直接操作 UI**(例外:ToolStrip 系控制項)
  • ProgressChanged:UI 執行緒,安全更新進度條、列表
  • RunWorkerCompleted:UI 執行緒,完成後收尾

實戰範例:目錄檔案複製並即時顯示進度


private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    var srcDir = (DirectoryInfo)e.Argument;
    int i = 1, total = srcDir.GetFiles().Length;

    foreach (var file in srcDir.GetFiles())
    {
        // 僅 ToolStrip 系控制項可在背景執行緒直接存取
        tsslCurrentFile.Text = $"正在複製:{file.Name}";

        file.CopyTo(Path.Combine(targetDir, file.Name), true);
        bw.ReportProgress((i * 100) / total, file.Name); // 觸發 ProgressChanged
        i++;
    }
    e.Result = total;
}

private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    pgb.Value = e.ProgressPercentage;                 // 更新進度條
    lvwFiles.Items.Add(new ListViewItem(new[] { DateTime.Now.ToLongTimeString(), (string)e.UserState })).EnsureVisible();
}

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    txtSource.Clear(); txtTarget.Clear();
    MessageBox.Show($"完成複製 {(int)e.Result} 個檔案");
}

執行畫面

模式二:Thread + Control.Invoke — 手動封送、彈性最高

適用於大量平行工作、需精細控制執行緒生命週期的場景。核心模式:


// 定義委派對應 UI 動作
delegate void ReportProgressDelegate(string fileName, int percent);
delegate void CompletedDelegate(string message);

private void UpdateUI(string file, int percent)
{
    if (InvokeRequired)
        Invoke(new ReportProgressDelegate(UpdateUI), file, percent);
    else
    {
        lvw.Items.Add(new ListViewItem(new[] { DateTime.Now.ToLongTimeString(), file })).EnsureVisible();
        pgb.Value = percent;
    }
}

private void OnCompleted(string msg)
{
    if (InvokeRequired)
        Invoke(new CompletedDelegate(OnCompleted), msg);
    else
    {
        txtSource.Clear(); txtTarget.Clear();
        MessageBox.Show(msg);
    }
}

private void WorkerThread(object srcDirObj)
{
    var dir = (DirectoryInfo)srcDirObj;
    int i = 1, total = dir.GetFiles().Length;

    foreach (var f in dir.GetFiles())
    {
        tsslCurrentFile.Text = $"處理中:{f.Name}"; // 僅 ToolStrip 安全
        f.CopyTo(Path.Combine(targetDir, f.Name), true);
        UpdateUI(f.Name, (i * 100) / total);
        i++;
    }
    OnCompleted($"共複製 {i-1} 個檔案");
}

// 啟動
new Thread(WorkerThread) { IsBackground = true }.Start(sourceDir);

關鍵差異對照表

面向BackgroundWorkerThread + Invoke
程式碼複雜度低(事件式)中(手動委派/Invoke)
取消支援內建 CancellationPending自行實作旗標
進度回報內建 ReportProgress自行 Invoke
例外處理RunWorkerCompletedEventArgs.Errortry-catch 傳遞至 UI
適用場景單一背景任務、簡單進度多執行緒、高併發、長生命週期
.NET Core/5+ 建議改用 Task + Progress<T>改用 Task.Run + Dispatcher/Control.Invoke

避坑鐵律

  1. 永遠檢查 InvokeRequired 再決定是否 Invoke,避免重複封送效能損耗。
  2. 傾向表單層級 Invoke(this.Invoke),而非單一控制項,減少跨執行緒切換次數。
  3. ToolStrip 例外:ToolStripStatusLabel 等可在背景執行緒直接存取,但不建議依賴,統一走 Invoke 更安全。
  4. 異常捕獲:背景執行緒未捕獲例外會終結進程,務必在 DoWork 或 Thread 入口點包裝 try-catch 並透過事件/委派回傳 UI。
  5. 現代化替代:新專案優先使用 Task.Run(() => ...) 搭配 Progress<T> 或 await + SynchronizationContext,程式碼更簡潔、可組合。

現代化寫法範例


private async void btnStart_Click(object sender, EventArgs e)
{
    var progress = new Progress<(string File, int Percent)>(p =>
    {
        pgb.Value = p.Percent;
        lvw.Items.Add(new ListViewItem(new[] { DateTime.Now.ToLongTimeString(), p.File })).EnsureVisible();
    });

    await Task.Run(() => CopyFilesWithProgress(sourceDir, targetDir, progress));
    MessageBox.Show("完成");
}

private void CopyFilesWithProgress(DirectoryInfo src, string dest, IProgress<(string, int)> progress)
{
    var files = src.GetFiles();
    for (int i = 0; i < files.Length; i++)
    {
        files[i].CopyTo(Path.Combine(dest, files[i].Name), true);
        progress.Report((files[i].Name, (i + 1) * 100 / files.Length));
    }
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved