WinForms/WPF 應用中,**UI 執行緒 (主執行緒) 單一且負責訊息泵**,若在 UI 執行緒執行耗時運算 (資料庫查詢、檔案 I/O、網路呼叫) 會導致介面凍結、無法回應使用者操作。正確架構:**UI 執行緒僅負責繪製與事件**,**耗時工作移至背景執行緒**,完成後**安全地回傳結果更新 UI**。
// ❌ 錯誤:跨執行緒存取控制項屬性,會拋 InvalidOperationException (Debug 模式) 或導致未定義行為
private void btnStart_Click(object sender, EventArgs e)
{
new Thread(() =>
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(50);
listView1.Items[i].SubItems[1].Text = i.ToString(); // 直接存取 UI
}
}).Start();
}
// ✅ 正確:使用 Invoke 將委派排入 UI 訊息佇列執行
private void btnStart_Click(object sender, EventArgs e)
{
new Thread(Worker) { IsBackground = true }.Start();
}
private void Worker()
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(50);
string text = i.ToString();
// 檢查是否需要跨執行緒
if (listView1.InvokeRequired)
{
listView1.Invoke(new Action(() =>
{
if (listView1.Items.Count > i)
listView1.Items[i].SubItems[1].Text = text;
}));
}
else
{
listView1.Items[i].SubItems[1].Text = text;
}
}
}
// ✅ 推薦:非同步、進度回報、取消支援、例外處理、自動切換同步內容
private async void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
var progress = new Progress(i =>
{
if (listView1.Items.Count > i)
listView1.Items[i].SubItems[1].Text = i.ToString();
});
var cts = new CancellationTokenSource();
btnCancel.Click += (s, e) => cts.Cancel();
try
{
await Task.Run(() => DoWork(progress, cts.Token), cts.Token);
MessageBox.Show("完成");
}
catch (OperationCanceledException)
{
MessageBox.Show("已取消");
}
catch (Exception ex)
{
MessageBox.Show($"錯誤: {ex.Message}");
}
finally
{
btnStart.Enabled = true;
}
}
private void DoWork(IProgress progress, CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(50); // 模擬耗時運算
progress.Report(i); // 自動捕獲呼叫端同步內容 (UI 執行緒)
}
}
| 類別/模式 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
Thread + Invoke | 舊專案、需精細控制執行緒生命週期 | 底層控制、可設定 Priority/IsBackground | 手動管理同步、易出錯、無取消/進度標準化 |
BackgroundWorker | .NET Framework 2.0~4.0 專案 | 內建進度/完成/取消事件、自動 Invoke | 已過時、不支援 async/await、單一背景作業 |
Task.Run + async/await | 現代 .NET (4.5+ / Core / 5+) | 語法簡潔、組合性強、支援取消/進度/例外傳播 | 需理解同步內容、ThreadPool 星檔風險 |
IProgress<T> + Progress<T> | 背景作業回報進度至 UI | 自動捕獲 SynchronizationContext、解耦回報邏輯 | 僅單向回報、高頻回報需節流 |
CancellationToken | 可取消的長時間作業 | 標準化取消協議、可傳遞多層 | 需定期檢查 ThrowIfCancellationRequested |
// 使用 BlockingCollection 實作執行緒安全佇列
private readonly BlockingCollection _queue = new();
private readonly CancellationTokenSource _cts = new();
public void StartProducer()
{
Task.Run(() =>
{
foreach (var item in GetDataSource())
{
_queue.Add(item, _cts.Token); // 佇列滿時阻塞
}
_queue.CompleteAdding();
}, _cts.Token);
}
public void StartConsumer(int workerCount)
{
for (int i = 0; i < workerCount; i++)
{
Task.Run(ConsumerLoop, _cts.Token);
}
}
private void ConsumerLoop()
{
foreach (var item in _queue.GetConsumingEnumerable(_cts.Token))
{
ProcessItem(item); // 耗時運算
// 進度回報...
}
}
public class ThrottledProgress : IProgress
{
private readonly IProgress _inner;
private readonly TimeSpan _interval;
private DateTime _lastReport = DateTime.MinValue;
private T _latestValue;
private readonly object _lock = new();
public ThrottledProgress(IProgress inner, TimeSpan interval)
{
_inner = inner; _interval = interval;
}
public void Report(T value)
{
lock (_lock)
{
_latestValue = value;
var now = DateTime.Now;
if (now - _lastReport >= _interval)
{
_lastReport = now;
_inner.Report(value);
}
}
}
// 確保最後一次值一定送達
public void Flush()
{
lock (_lock)
{
_inner.Report(_latestValue);
}
}
}
// 使用
var throttled = new ThrottledProgress(progress, TimeSpan.FromMilliseconds(200));
await Task.Run(() => DoWork(throttled, token));
throttled.Flush();
InvokeRequired 檢查、或在非 UI 執行緒建立控制項。解決:統一用 Progress<T> 或 Invoke。Invoke 等待 UI,UI 執行緒又 Wait() 背景 Task。解決:UI 端一律 await,勿用 .Result/.Wait()。this、事件未取消訂閱、CancellationToken 未釋放。解決:WeakReference、IDisposable 模式、using 區塊。TaskCreationOptions.LongRunning 建立專用執行緒、或使用 SemaphoreSlim 限制並行度。Debug.WriteLine($"ThreadId: {Thread.CurrentThread.ManagedThreadId}"):追蹤程式碼執行緒歸屬。private async void OnStartClick(object sender, EventArgs e)
{
var progress = new Progress(UpdateUI);
var cts = new CancellationTokenSource();
btnCancel.Click += (s, e) => cts.Cancel();
SetUIEnabled(false);
try
{
await Task.Run(() => LongRunningWork(progress, cts.Token), cts.Token);
ShowMessage("完成");
}
catch (OperationCanceledException) { ShowMessage("取消"); }
catch (Exception ex) { ShowError(ex); }
finally { SetUIEnabled(true); }
}
private void LongRunningWork(IProgress progress, CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
// 耗時運算...
progress.Report(i);
}
}
private void UpdateUI(int value) => /* 安全更新控制項 */;