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

[C# 學習] 多執行緒中如何安全呼叫 WinForm 控制項

     編輯:關於C語言

問題由來

WinForm 程式中有一個更新主視窗的工作執行緒,但文件提示不能在多執行緒中呼叫這個 Form(為什麼?),實際呼叫時程式常當機。如何從多執行緒中呼叫 Form 中的方法?

根本原因

每個從 Control 衍生的 WinForm 類別(含 Control 本身)都仰賴底層 Windows 訊息與訊息泵迴圈執行。訊息迴圈必須有對應執行緒,因為發送給視窗的訊息實際只會發送到建立該視窗的執行緒。結果:即使提供同步化,也無法從多執行緒呼叫這些處理訊息的方法。大多數管線被隱藏,因 WinForm 用委派將訊息繫結到事件處理方法。WinForm 將 Windows 訊息轉換為委派型事件,但仍必須注意:只有建立該 Form 的執行緒才能呼叫其事件處理方法。若在自己的執行緒呼叫,會在該執行緒處理事件而非指定執行緒。可從任何執行緒呼叫非訊息處理方法。

解決方案:ISynchronizeInvoke 介面

Control 類別實作 System.ComponentModel.ISynchronizeInvoke 介面處理多執行緒呼叫訊息處理方法:

public interface ISynchronizeInvoke
{
    object Invoke(Delegate method, object[] args);
    IAsyncResult BeginInvoke(Delegate method, object[] args);
    object EndInvoke(IAsyncResult result);
    bool InvokeRequired { get; }
}

ISynchronizeInvoke 提供標準機制在其他執行緒物件中進行方法呼叫。若物件實作此介面,執行緒 T1 上的用戶端可呼叫其 Invoke() 方法。Invoke() 會阻塞該執行緒呼叫,將呼叫封送至 T2 並在 T2 執行,再將回傳值封送回 T1 返回用戶端。Invoke() 以委派定位 T2 中調用,以物件陣列作參數。

InvokeRequired 屬性檢查

呼叫者可檢查 InvokeRequired,因可在同一執行緒呼叫也可重導向至其他執行緒。若回傳 false 可直接呼叫物件方法。

範例:從其他執行緒關閉 Form

Form form;
// 取得 form 參考後:
ISynchronizeInvoke synchronizer = form;
if (synchronizer.InvokeRequired)
{
    MethodInvoker invoker = new MethodInvoker(form.Close);
    synchronizer.Invoke(invoker, null);
}
else
    form.Close();

ISynchronizeInvoke 不限於 WinForm

例如 Calculator 類別提供 Add() 方法,透過 ISynchronizeInvoke 實現。用戶必須確定 Invoke() 呼叫在正確執行緒執行。

計算機類別完整實作

public class Calculator : ISynchronizeInvoke
{
    public int Add(int arg1, int arg2)
    {
        int threadID = Thread.CurrentThread.GetHashCode();
        Trace.WriteLine("Calculator thread ID is " + threadID);
        return arg1 + arg2;
    }
    // ISynchronizeInvoke 實作...
}

// 用戶端代碼
public delegate int AddDelegate(int arg1, int arg2);
int threadID = Thread.CurrentThread.GetHashCode();
Trace.WriteLine("Client thread ID is " + threadID);
Calculator calc = new Calculator();
AddDelegate addDelegate = new AddDelegate(calc.Add);
object[] arr = new object[2] { 3, 4 };
int sum = (int)calc.Invoke(addDelegate, arr);
Debug.Assert(sum == 7);

非同步呼叫:BeginInvoke / EndInvoke

若不想同步呼叫(被封送至其他執行緒),可用 BeginInvoke()/EndInvoke() 依 .NET 非同步程式模式:BeginInvoke() 發送調用,EndInvoke() 等待完成或完成時通知並收集結果。

注意事項

  • 非型別安全:型別不符在執行時拋出異常而非編譯錯誤,使用需格外小心。
  • 實作需使用委派在晚期繫結中動態調用方法。每種委派型別提供 DynamicInvoke(object[] args)。
  • 理論上必須將方法委派放入提供物件執行真實執行緒,並使 Invoke()/BeginInvoke() 中委派調用 DynamicInvoke()。
  • 附帶原始碼含 Synchronizer 輔助類別與測試程式,驗證 Calculator 如何用 Synchronizer 實作 ISynchronizeInvoke。Synchronizer 為通用實作,可用其衍生類或本身作為物件使用。
  • 關鍵元素:WorkerThread 巢狀類別含工作項目佇列,WorkItem 包含方法委派與參數。Invoke()/BeginInvoke() 加入工作項目。WorkerThread 建立 .NET worker 執行緒監測佇列,查詢到項目後讀取並調用 DynamicInvoke()。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved