C# 多執行緒程(Multithreading)【三】UI操作

在 C# 中,當你在使用 多執行緒(Thread) 的時候,如果嘗試直接從非 UI 執行緒存取或操作 UI 控制項(

像是 Form 上的 Label、TextBox 等),通常會遇到例外,例如:

InvalidOperationException: 跨執行緒作業無效: 存取控制項 'XXX' 時所使用的執行緒與建立控制項的執行緒不同。

這是因為 Windows Forms 的 UI 控制項不是執行緒安全的。你必須使用 主執行緒(UI 執行緒) 來操作 UI 控制項。


正確做法:使用 InvokeBeginInvoke

範例:從背景執行緒更新 Label 的文字

private void UpdateLabelFromThread()
{
    Thread thread = new Thread(() =>
    {
        string newText = "從執行緒更新 UI";

        // 使用 Invoke 確保在 UI 執行緒上操作 UI
        if (label1.InvokeRequired)
        {
            label1.Invoke(new Action(() =>
            {
                label1.Text = newText;
            }));
        }
        else
        {
            label1.Text = newText;
        }
    });

    thread.Start();
}

補充說明

  • InvokeRequired:檢查是否需要透過 Invoke 呼叫。當前執行緒不是 UI 執行緒時會回傳 true。
  • Invoke:同步執行,會等操作完成。
  • BeginInvoke:非同步執行,操作不會阻塞呼叫的執行緒。


更簡單方式:使用 BackgroundWorker(適用舊專案)

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
    // 背景執行的工作
    string result = "背景工作完成";

    // 回傳給 RunWorkerCompleted
    e.Result = result;
};
worker.RunWorkerCompleted += (s, e) =>
{
    // 在 UI 執行緒操作 UI
    label1.Text = e.Result.ToString();
};
worker.RunWorkerAsync();


更現代方式(建議):使用 async/await 搭配 Task

private async void button1_Click(object sender, EventArgs e)
{
    label1.Text = "工作中...";

    string result = await Task.Run(() =>
    {
        // 模擬長時間工作
        Thread.Sleep(2000);
        return "工作完成";
    });

    label1.Text = result; // 自動回到 UI 執行緒
}

這種方式不需要 Invoke,因為 await 完成後會自動切回 UI 執行緒,非常適合 WinForms 或 WPF。


留言