WinForm 程式中有一個更新主視窗的工作執行緒,但文件提示不能在多執行緒中呼叫這個 Form(為什麼?),實際呼叫時程式常當機。如何從多執行緒中呼叫 Form 中的方法?
每個從 Control 衍生的 WinForm 類別(含 Control 本身)都仰賴底層 Windows 訊息與訊息泵迴圈執行。訊息迴圈必須有對應執行緒,因為發送給視窗的訊息實際只會發送到建立該視窗的執行緒。結果:即使提供同步化,也無法從多執行緒呼叫這些處理訊息的方法。大多數管線被隱藏,因 WinForm 用委派將訊息繫結到事件處理方法。WinForm 將 Windows 訊息轉換為委派型事件,但仍必須注意:只有建立該 Form 的執行緒才能呼叫其事件處理方法。若在自己的執行緒呼叫,會在該執行緒處理事件而非指定執行緒。可從任何執行緒呼叫非訊息處理方法。
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,因可在同一執行緒呼叫也可重導向至其他執行緒。若回傳 false 可直接呼叫物件方法。
Form form;
// 取得 form 參考後:
ISynchronizeInvoke synchronizer = form;
if (synchronizer.InvokeRequired)
{
MethodInvoker invoker = new MethodInvoker(form.Close);
synchronizer.Invoke(invoker, null);
}
else
form.Close();
例如 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() 依 .NET 非同步程式模式:BeginInvoke() 發送調用,EndInvoke() 等待完成或完成時通知並收集結果。
DynamicInvoke(object[] args)。Invoke()/BeginInvoke() 中委派調用 DynamicInvoke()。Synchronizer 輔助類別與測試程式,驗證 Calculator 如何用 Synchronizer 實作 ISynchronizeInvoke。Synchronizer 為通用實作,可用其衍生類或本身作為物件使用。WorkerThread 巢狀類別含工作項目佇列,WorkItem 包含方法委派與參數。Invoke()/BeginInvoke() 加入工作項目。WorkerThread 建立 .NET worker 執行緒監測佇列,查詢到項目後讀取並調用 DynamicInvoke()。