.NET 2.0+ (VS 2005) 為保證線程安全,默認禁止跨線程直接訪問 UI 控件。違規操作會拋出 InvalidOperationException:「跨線程操作無效: 從不是創建控件的線程訪問它。」
Control.CheckForIllegalCrossThreadCalls = false;
缺點:繞過安全機制,可能導致界面錯亂、死鎖、數據競爭,生產環境嚴禁使用。
核心原理:Control.InvokeRequired 判斷是否需跨線程,Invoke 將委托排入 UI 線程消息隊列執行。
using System;
using System.Threading;
using System.Windows.Forms;
namespace ThreadSafeUI
{
public class WorkerThread
{
private readonly Control _targetControl;
private readonly int _seed;
private Thread _thread;
// 定義委托類型
public delegate void SetTextCallback(string text);
public WorkerThread(Control control, int seed)
{
_targetControl = control;
_seed = seed;
_thread = new Thread(Run);
_thread.Start();
}
// 線程主邏輯
private void Run()
{
Random rand = new Random(_seed);
while (true)
{
string text = rand.Next(0, 10).ToString();
UpdateUI(text); // 安全更新 UI
Thread.Sleep(100);
}
}
// 線程安全的 UI 更新方法
private void UpdateUI(string text)
{
if (_targetControl.InvokeRequired)
{
// 跨線程:創建委托實例,通過 Invoke 執行
SetTextCallback callback = new SetTextCallback(UpdateUI);
_targetControl.Invoke(callback, new object[] { text });
}
else
{
// 同線程:直接賦值
_targetControl.Text = text;
}
}
public void Stop()
{
if (_thread != null && _thread.IsAlive)
_thread.Abort(); // 注意:Abort 有風險,生產環境建議用 CancellationToken
}
}
}
public partial class MainForm : Form
{
private WorkerThread _worker1, _worker2, _worker3;
private bool _isRunning = false;
private void btnStartStop_Click(object sender, EventArgs e)
{
if (!_isRunning)
{
_worker1 = new WorkerThread(label1, 1000);
_worker2 = new WorkerThread(label2, 1111);
_worker3 = new WorkerThread(label3, 2222);
_isRunning = true;
btnStartStop.Text = "停止";
}
else
{
_worker1?.Stop();
_worker2?.Stop();
_worker3?.Stop();
_isRunning = false;
btnStartStop.Text = "開始";
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_worker1?.Stop();
_worker2?.Stop();
_worker3?.Stop();
base.OnFormClosing(e);
}
}
// 現代寫法:配合 Task.Run 與 Progress
private async void btnModern_Click(object sender, EventArgs e)
{
var progress = new Progress(text => label1.Text = text);
await Task.Run(() => {
Random r = new Random();
for (int i = 0; i < 100; i++) {
progress.Report(r.Next(0, 10).ToString());
Thread.Sleep(100);
}
});
}
InvokeRequired:避免同線程調用 Invoke 產生額外開銷Invoke 會重新拋出目標方法異常,需在 UI 線程捕獲Thread.Abort():使用 CancellationToken 或標誌位優雅停止BackgroundWorker 或 Task:封裝度更高,不易出錯