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

C# WinForms 子執行緒更新 ProgressBar 完整範例 (Invoke / SynchronizationContext)

     編輯:關於C#

WinForms 控制項具有執行緒親和性,只能在建立它們的 UI 執行緒存取。子執行緒若直接操作 ProgressBar.Value 會拋出 InvalidOperationException。本文示範標準 Invoke 模式與現代 Progress<T> / SynchronizationContext 兩種解法。

場景:模擬下載進度回報

方案一:傳統 Invoke 模式 (相容 .NET Framework 所有版本)


using System;
using System.Threading;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public Form1() { InitializeComponent(); }

    private void btnStart_Click(object sender, EventArgs e)
    {
        // 啟動子執行緒
        new Thread(StartDownload).Start();
    }

    private void StartDownload()
    {
        var downloader = new Downloader();
        downloader.ProgressChanged += Downloader_ProgressChanged;
        downloader.Start();
    }

    private void Downloader_ProgressChanged(long total, long current)
    {
        // 檢查是否需要跨執行緒委派
        if (this.InvokeRequired)
        {
            // 使用委派安全回呼 UI 執行緒
            this.Invoke(new Action(Downloader_ProgressChanged), total, current);
            return;
        }

        // UI 執行緒安全更新控制項
        progressBar1.Maximum = (int)total;
        progressBar1.Value = (int)current;
        lblStatus.Text = $"{current} / {total}";
    }
}

public class Downloader
{
    public delegate void ProgressHandler(long total, long current);
    public event ProgressHandler ProgressChanged;

    public void Start()
    {
        for (int i = 0; i <= 100; i++)
        {
            ProgressChanged?.Invoke(100, i);
            Thread.Sleep(50); // 模擬工作
        }
    }
}

方案二:現代 IProgress<T> + Progress<T> (推薦 .NET 4.5+)

Progress<T> 會捕捉建立時的 SynchronizationContext (UI 執行緒),自動將回呼排程到 UI 執行緒,**無需手動 Invoke**。


private async void btnStartModern_Click(object sender, EventArgs e)
{
    var progress = new Progress(ReportProgress);
    await Task.Run(() => StartDownloadAsync(progress));
}

private void ReportProgress(DownloadProgress p)
{
    // 自動在 UI 執行緒執行,無需 Invoke
    progressBar1.Maximum = p.Total;
    progressBar1.Value = p.Current;
    lblStatus.Text = $"{p.Current} / {p.Total} ({p.Percentage:P0})";
}

private void StartDownloadAsync(IProgress progress)
{
    for (int i = 0; i <= 100; i++)
    {
        progress.Report(new DownloadProgress { Total = 100, Current = i });
        Thread.Sleep(50);
    }
}

public class DownloadProgress
{
    public long Total { get; set; }
    public long Current { get; set; }
    public double Percentage => Total == 0 ? 0 : (double)Current / Total;
}

關鍵比較

特性Invoke / BeginInvokeProgress<T> / IProgress<T>
適用版本.NET Framework 1.0+.NET 4.5+ / .NET Core / .NET 5+
程式碼複雜度需檢查 InvokeRequired、宣告委派極簡,自動捕捉上下文
非同步配合需自行包裝 Task天生支援 async/await
效能同步 Invoke 會阻塞呼叫執行緒非同步排程,不阻塞回報執行緒
單元測試難以模擬 UI 執行緒可注入 Mock IProgress<T>

常見錯誤避坑

  • 直接在子執行緒存取控制項屬性:一定會拋例外,必須透過 Invoke 或 Progress<T>。
  • 使用 Control.CheckForIllegalCrossThreadCalls = false:僅隱藏例外,實際上仍不安全,嚴禁生產環境使用。
  • 頻繁回報導致 UI 凍結:進度更新太密集 (如每 1ms) 會塞滿 UI 訊息佇列,建議節流 (如每 100ms 或 1% 回報一次)。
  • Invoke 死結:子執行緒呼叫 Invoke 等待 UI,UI 執行緒又在等待子執行緒 (如 Thread.Join),導致死結。改用 BeginInvoke 或 Progress<T> 非同步模式。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved