WinForms 控制項具有執行緒親和性,只能在建立它們的 UI 執行緒存取。子執行緒若直接操作 ProgressBar.Value 會拋出 InvalidOperationException。本文示範標準 Invoke 模式與現代 Progress<T> / SynchronizationContext 兩種解法。
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); // 模擬工作
}
}
}
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 / BeginInvoke | Progress<T> / IProgress<T> |
|---|---|---|
| 適用版本 | .NET Framework 1.0+ | .NET 4.5+ / .NET Core / .NET 5+ |
| 程式碼複雜度 | 需檢查 InvokeRequired、宣告委派 | 極簡,自動捕捉上下文 |
| 非同步配合 | 需自行包裝 Task | 天生支援 async/await |
| 效能 | 同步 Invoke 會阻塞呼叫執行緒 | 非同步排程,不阻塞回報執行緒 |
| 單元測試 | 難以模擬 UI 執行緒 | 可注入 Mock IProgress<T> |
Control.CheckForIllegalCrossThreadCalls = false:僅隱藏例外,實際上仍不安全,嚴禁生產環境使用。Invoke 等待 UI,UI 執行緒又在等待子執行緒 (如 Thread.Join),導致死結。改用 BeginInvoke 或 Progress<T> 非同步模式。