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

C# WinForms 多執行緒存取 UI 控制項完整解決方案

     編輯:關於C#

開發 WinForms 應用程式時,大部分情況會遇到多執行緒控制介面控制項的問題。然而無法用傳統方法解決,下面詳細介紹幾種方案並分析優缺點。

問題重現

public partial class Form1 : Form {
    public Form1() { InitializeComponent(); }
    private void Form1_Load(object sender, EventArgs e) {
        Thread thread = new Thread(ThreadFunction);
        thread.IsBackground = true;
        thread.Start();
    }
    private void ThreadFunction() {
        while (true) {
            this.textBox1.Text = DateTime.Now.ToString();
            Thread.Sleep(1000);
        }
    }
}

執行會拋出例外:Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on。.NET 2.0 後加強安全機制,不允許 WinForms 直接跨執行緒存取控制項屬性。

方案一:關閉跨執行緒檢查(不推薦)

private void Form1_Load(object sender, EventArgs e) {
    Control.CheckForIllegalCrossThreadCalls = false; // 關閉檢查
    Thread thread = new Thread(ThreadFunction);
    thread.IsBackground = true;
    thread.Start();
}

程式可正常執行,但 CheckForIllegalCrossThreadCalls 是 static 屬性,修改後全域生效。專案中其他人修改此值會導致方案失效,且跨執行緒存取通常應檢查,不可取。

方案二:使用 BeginInvoke 將執行緒「注入」主執行緒(有缺陷)

private delegate void FlushClient();
private void CrossThreadFlush() {
    FlushClient fc = new FlushClient(ThreadFunction);
    this.BeginInvoke(fc); // 調用委派
}
private void ThreadFunction() {
    while (true) {
        this.textBox1.Text = DateTime.Now.ToString();
        Thread.Sleep(1000);
    }
}

雖無跨執行緒例外,但介面無回應。原因:這相當於把新執行緒「注入」主控制執行緒,取得主執行緒控制權。只要此執行緒不返回,主執行緒永遠無法回應。即使用有限迴圈能返回,多執行緒也失去原本意義。

方案三:正確的 Invoke 模式(推薦)

public partial class Form1 : Form {
    private delegate void FlushClient();
    public Form1() { InitializeComponent(); }
    private void Form1_Load(object sender, EventArgs e) {
        Thread thread = new Thread(CrossThreadFlush);
        thread.IsBackground = true;
        thread.Start();
    }
    private void CrossThreadFlush() {
        while (true) {
            Thread.Sleep(1000); // Sleep 與迴圈放在非同步等待外部
            ThreadFunction();
        }
    }
    private void ThreadFunction() {
        if (this.textBox1.InvokeRequired) { // 等待非同步
            FlushClient fc = new FlushClient(ThreadFunction);
            this.Invoke(fc); // 透過委派呼叫重新整理方法
        } else {
            this.textBox1.Text = DateTime.Now.ToString();
        }
    }
}

關鍵點解析

  1. InvokeRequired 檢查:判斷目前執行緒是否為建立控制項的執行緒,若否則需透過 Invoke 封送。
  2. 迴圈與 Sleep 放在外層:背景執行緒負責迴圈與延遲,只在需要更新 UI 時透過 Invoke 短暫切換到主執行緒,釋放主執行緒處理其他訊息。
  3. Invoke vs BeginInvoke:Invoke 同步等待完成,BeginInvoke 非同步。此處用 Invoke 確保更新順序與簡單錯誤處理。

進階:封裝通用跨執行緒更新助手

public static class UIThreadHelper {
    public static void InvokeIfRequired(this Control control, Action action) {
        if (control.InvokeRequired)
            control.Invoke(action);
        else
            action();
    }
}

// 使用範例
private void BackgroundWork() {
    while (running) {
        Thread.Sleep(1000);
        string time = DateTime.Now.ToString();
        this.textBox1.InvokeIfRequired(() => this.textBox1.Text = time);
    }
}

執行上述正確程式碼,問題已解決。透過非同步等待,不會持續佔有主執行緒控制權,可在不發生跨執行緒呼叫例外情況下完成多執行緒對 WinForms 控制項控制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved