無(wú)論是WIN32還是Windows Form還是WPF還是Swing,都不支持GUI線程之外的線程直接訪問(wèn)其API。今天我們來(lái)回顧一下這個(gè)發(fā)展過(guò)程。一個(gè)普通的操作是怎么被封裝封裝再封裝的。
Win32
在Windows SDK時(shí)代,我們都知道,界面就是一個(gè)大的WndProc控制的。
switch (message)
{
case WM_PAINT:

case WM_DESTROY:

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
如果我們需要另外一個(gè)線程去做一些耗時(shí)的IO操作,同時(shí)要回調(diào)回來(lái)更新的界面,這個(gè)時(shí)候要么自己定義一個(gè)回調(diào)隊(duì)列,然后在WM_IDLE的時(shí)候處理,要么就利用窗口本身的消息隊(duì)列,自己創(chuàng)建一個(gè)消息類(lèi)型:
// at startup
int OUR_APP_IO_COMPLETED = RegisterWindowMessage("OUR_APP_IO_COMPLETED");
----
// after io completed
PostMessage(hwnd, OUR_APP_IO_COMPLETED, xxx, xxx);
這樣,我們就可以讓界面更新的操作在GUI線程完成了。
.NET 1.0 Windows Forms
Windows Forms的出現(xiàn)使得很多東西簡(jiǎn)單多了。跨線程的GUI操作也簡(jiǎn)化了許多。因?yàn)槲覀冇辛薉elegate和Control.Invoke。前者就是一個(gè)函數(shù)指針的升級(jí)版,Control.Invoke就是PostMessage的升級(jí)版:
public delegate void IOCompletedHandler();
public void OnIOCompleted() {
control.Invoke(new IOCompletedHandler(HandleIOCompleted));
}
public void HandleIOCompleted() {
}
在內(nèi)部Control維護(hù)了一個(gè)threadCallbackList。調(diào)用Invoke相當(dāng)于
1、把Delegate和參數(shù)封裝到一起,壓入到threadCallbackList中
2、確保ThreadCallbackMessage被注冊(cè)了(RegisterWindowMessage)
3、PostMessage,觸發(fā)Callback
4、WaitForHandle,等待GUI線程完成處理(如果是BeginInvoke則可以省略這個(gè)步驟)
而Windows Forms的WndProc的消息循環(huán),就需要加一個(gè)case語(yǔ)句,來(lái)響應(yīng)PostMessage。在得到消息的同時(shí),去threadCallbackList中取出需要回調(diào)的Delegate,然后一一調(diào)用。
Control.Invoke確實(shí)是讓我們的生活方便很多。但是它還是不能避免所謂的BeginInvoke之舞(BeginInvoke Dance):
//http://ikriv.com:8765/en/prog/info/dotnet/MysteriousHang.html
delegate void MyHandlerDelegate();
void MyHandler()
{
// "The BeginInvoke dance"
if (this.InvokeRequired) // assuming this descends from Control
{
BeginInvoke( new MyHandlerDelegate(MyHandler) );
return;
}
// assume we are on the main GUI thread
do GUI stuff
}
InvokeRequired內(nèi)部實(shí)現(xiàn)就是線程安全地比較一下當(dāng)前線程和GUI線程的線程ID是不是相等的。
另外Control.Invoke還需要我們確保對(duì)應(yīng)的Control的句柄已經(jīng)創(chuàng)建。為了避免這個(gè)問(wèn)題,WinForm有一個(gè)MarshingControl可以給我們使用。
Application.ThreadContext.FromCurrent().MarshingControl;
.NET 2.0 Windows Forms
如果我提供了一個(gè)回調(diào)接口。我一般會(huì)直接調(diào)用回調(diào)方法,如果我知道回調(diào)方法必須在GUI線程中執(zhí)行的話,用必須用Control.Invoke來(lái)調(diào)用回調(diào)方法。所以,在缺乏信息的情況下,回調(diào)接口的提供者是沒(méi)有辦法保證回調(diào)方法的執(zhí)行線程的環(huán)境的。所以,按照Strategy模式,我們可以定義一個(gè)CallbackStrategy
public interface CallbackExecutor {
void Execute(Delegate callback, object[] args);
}
public class DefaultCallbackExecutor : CallbackExecutor {
public void Execute(Delegate callback, object[] args) {
callback.Invoke(args);
}
}
public class WindowsFormsCallbackExecutor : CallbackExecutor {
private Control marshalingControl;
public WindowsFormsCallbackExecutor() {
Application.ThreadContext context = Application.ThreadContext.FromCurrent();
if (context != null)
{
this.marshalingControl= context.MarshalingControl;
}
}
public void Execute(Delegate callback, object[] args) {
marshalingControl.Invoke(callback, args);
}
}
然后回調(diào)方法的提供者就不用為難了,它只要調(diào)用CallbackExecutor就可以了,同時(shí)回調(diào)方法也可以安心地直接去干自己的事情。從而,誰(shuí)都不需要做BeginInvoke Dance了。
這就是.NET引入的SynchronizationContext的基本原理。由于微軟不是接口愛(ài)好者,所以它省去了共同的那個(gè)接口,直接讓W(xué)indowsFormSynchronizationContext繼承自SynchronizationContext類(lèi)了。SynchronizationContext是線程唯一的,取得當(dāng)前線程的Sync Context可以使用:
SynchronizationContext.Current.Post(delegate{//your code}, null);
與SynchronizationContext類(lèi)同時(shí)引入的還有AsyncOperationManager,AsyncOperation。它又是SynchronizationContext的封裝。一般來(lái)說(shuō),推薦使用AsyncOperationManager。AsyncOperationManager在創(chuàng)建AsyncOperation的時(shí)候會(huì)取得當(dāng)前線程的Sync Context,并存儲(chǔ)在SyncOperation之中。 所以后來(lái)利用AsyncOperation來(lái)回調(diào)的時(shí)候,就會(huì)用一開(kāi)始創(chuàng)建時(shí)候所在線程的context來(lái)回調(diào)。
// initialize in GUI thread
asyncOperation = AsyncOperationManager.Create(null);
// when you want to call back
asyncOperation.Post(delegate{//your code}, null);
像BackgroundWorker這樣的類(lèi),之所以其Callback可以直接訪問(wèn)GUI,其秘訣就在于回調(diào)不是直接調(diào)用的,而是有 AsyncOperation來(lái)間接代勞的。只要AsyncOperation是在GUI線程創(chuàng)建的,所有由它代勞的回調(diào)都會(huì)經(jīng)過(guò) MarshalingControl來(lái)做Control.Invoke。
.NET 3.0 WPF
Control.Invoke變成了Dispatcher.Invoke。內(nèi)部實(shí)現(xiàn)還是一樣的,有一個(gè)Callback隊(duì)列,然后PostMessage。同時(shí)引入的還有DispatcherSynchronizationContext。
Conculsion
從直接PostMessage到Control.Invoke到SynchronizationContext到AsyncOperationManager到BackgroundWorker,職責(zé)最終推卸到了回調(diào)的提供方。但是由于引入的時(shí)間不一致,很多回調(diào)提供方是沒(méi)有使用AsyncOperation的。這個(gè)時(shí)候,我們自己需要自己去包裝,比如:
Code
這樣,我們就不需要在手工地去用Control.Invoke了,也不應(yīng)該再直接用Control.Invoke了。
浙公網(wǎng)安備 33010602011771號(hào)