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

C# 執行緒無法正常開啟視窗問題:原因分析與 Invoke 解決方案

     編輯:關於C語言

問題現象

主視窗執行緒建立子執行緒監聽 Socket,子執行緒收到資料後引發事件,主視窗處理事件時建立新視窗 (new Form().Show())。結果視窗出現但「反白、無回應、標題列顯示未回應」,像死當一樣。

根本原因

  • Windows Forms 控制項具有執行緒親和性:必須在建立它們的執行緒 (UI 執行緒) 上存取。
  • 事件雖然在主視窗類別中定義,但由子執行緒引發,所以事件處理器實際運行在子執行緒上。
  • 子執行緒上 Form.Show() 建立視窗,但該視窗沒有訊息迴圈 (Application.Run),無法處理 WM_PAINT 等訊息,導致畫面不重繪、無法互動。

解決方案:Control.Invoke / BeginInvoke

將建立視窗的代碼封裝在委派中,透過 Invoke 切換到 UI 執行緒執行。

修正前 (錯誤)


// 子執行緒中
private void OnDataReceived(object sender, DataEventArgs e)
{
    var frm = new ChildForm(); // 在子執行緒建立
    frm.Show();                // 視窗無訊息泵,掛起
}

修正後 (正確)


// 定義委派
private delegate void ShowChildFormDelegate();

private void OnDataReceived(object sender, DataEventArgs e)
{
    // 檢查是否需要跨執行緒
    if (this.InvokeRequired)
    {
        // 切換到 UI 執行緒同步執行
        this.Invoke(new ShowChildFormDelegate(ShowChildForm));
    }
    else
    {
        ShowChildForm();
    }
}

private void ShowChildForm()
{
    var frm = new ChildForm();
    frm.Show(); // 現在在 UI 執行緒,正常運作
}

Invoke vs BeginInvoke

方法行為適用場景
Invoke同步,阻塞呼叫執行緒直到 UI 執行緒完成需等待 UI 更新完成再繼續
BeginInvoke非同步,立即返回,UI 執行緒稍後執行不需等待、避免死結、高頻率更新

完整範例:非同步委派模式 (APM)


using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

public delegate bool FactorizeDelegate(int n, ref int f1, ref int f2);

public class PrimeFactorizer
{
    public bool Factorize(int n, ref int f1, ref int f2)
    {
        f1 = 1; f2 = n;
        for (int i = 2; i < n; i++)
            if (n % i == 0) { f1 = i; f2 = n / i; break; }
        return f1 != 1;
    }
}

public class ResultHandler
{
    private int _number;
    public ResultHandler(int n) => _number = n;

    [System.Runtime.Remoting.Messaging.OneWay]
    public void OnCompleted(IAsyncResult ar)
    {
        var del = (FactorizeDelegate)((AsyncResult)ar).AsyncDelegate;
        del.EndInvoke(ref f1, ref f2, ar);
        Console.WriteLine($"[{_number}] 因子: {f1} x {f2}");
    }
    private int f1, f2;
}

public class Program
{
    public static void Main()
    {
        var factorizer = new PrimeFactorizer();
        var del = new FactorizeDelegate(factorizer.Factorize);
        var handler = new ResultHandler(1000589023);

        // 非同步呼叫 + 回呼
        del.BeginInvoke(1000589023, ref _, ref _, handler.OnCompleted, null);

        Console.WriteLine("主執行緒繼續做其他事...");
        Thread.Sleep(2000); // 等待示範
    }
    private static int _;
}

現代替代方案:async/await + Task.Run


// UI 事件處理器
private async void btnStart_Click(object sender, EventArgs e)
{
    btnStart.Enabled = false;
    try
    {
        // 背景執行緒運算
        var result = await Task.Run(() => HeavyCompute());
        // 自動切回 UI 執行緒
        txtResult.Text = result;
    }
    finally { btnStart.Enabled = true; }
}

最佳實務清單

  1. 所有 UI 控制項存取必須在 UI 執行緒,使用 InvokeRequired 檢查。
  2. 長時間運算放入 Task.Run,await 後自動回 UI 執行緒。
  3. 避免在建構子/ Load 事件中做耗時工作,改用 Shown 或背景載入。
  4. 定期清理事件訂閱 (-=) 避免記憶體洩漏。
  5. 使用 SynchronizationContext 或 TaskScheduler.FromCurrentSynchronizationContext() 進行進階排程。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved