問題:在多線程中調用Winform
我的WinForm程序中有一個用於更新主窗口的工作線程(worker thread),但文檔中卻提示我不能在多線程中調用這個form(為什麼?),而事實上我在調用時程序常常會崩掉。請問如何從多線程中調用form中的方法呢?
解答:
每一個從Control類中派生出來的WinForm類(包括Control類)都是依靠底層Windows消息和一個消息泵循環(message pump loop)來執行的。消息循環都必須有一個相對應的線程,因為發送到一個window的消息實際上只會被發送到創建該window的線程中去。其結果是,即使提供了同步(synchronization),你也無法從多線程中調用這些處理消息的方法。大多數plumbing是掩藏起來的,因為WinForm是用代理(delegate)將消息綁定到事件處理方法中的。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提供了一個普通的標准機制用於在其他線程的對象中進行方法調用。例如,如果一個對象實現了ISynchronizeInvoke,那麼在線程T1上的客戶端可以在該對象中調用ISynchronizeInvoke的Invoke()方法。Invoke()方法的實現會阻塞(block)該線程的調用,它將調用打包發送(marshal)到 T2,並在T2中執行調用,再將返回值發送會T1,然後返回到T1的客戶端。Invoke()方法以一個代理來定位該方法在T2中的調用,並以一個普通的對象數組做為其參數。
調用者還可以檢查InvokeRequired屬性,因為你既可以在同一線程中調用ISynchronizeInvoke也可以將它重新定位(redirect)到其他線程中去。如果InvokeRequired的返回值是false的話,則調用者可以直接調用該對象的方法。
比如,假設你想要從另一個線程中調用某個form中的Close方法,那麼你可以使用預先定義好的的MethodInvoker代理,並調用Invoke方法:
Form form;
/* obtain a reference to the form,
then: */
ISynchronizeInvoke synchronizer;
synchronizer = form;
if(synchronizer.InvokeRequired)
{
MethodInvoker invoker = new
MethodInvoker(form.Close);
synchronizer.Invoke(invoker,null);
}
else
form.Close();