Invoke 和 BeginInvoke 的區別

討論環境:C# .netVS2005

.net默認所有的可視窗體在主線程內維護,如果某工作線程(主線程之外)想實現對窗體控件的操作,缺省情況下是不允許直接操作的,而要通過 Invoke 方法將其封送到主線程去完成。在Control 類內提供了 Invoke  BeginInvoke 兩個方法實現該功能,MSDN 幫助中提到,它們的唯一區別是 BeginInvoke 多了“異步執行”四個字。(兩方法的具體幫助請自行查看MSDN,這裏不多羅嗦了)。

“異步執行”怎麼理解,查了網上的一些解答,通過Reflector查看了兩方法的背後源碼後,得出如下結論:

Invoke 引起工作線程的阻塞,BeginInvoke 不引起工作線程的阻塞。

具體解釋一下:我們先假設稱主線程(即窗體控件的擁有者)爲A線程,工作線程爲B線程,如果B線程需要操作窗體控件,那麼就要使用Invoke(或BeginInvoke),將相應的操作通過代理,封送到主線程A(具體的代碼實現,不多羅嗦,假設讀者已知)。那麼.net背後是怎麼實現線程間“任務挪移”這一步操作的呢?通過Reflector查看源碼後發現,原來 Invoke 將你交給它的委託封裝成了一個標準的Windows消息,加進了主線程的消息隊列內。回到Invoke  BeginInvoke 的區別上來,如果使用 Invoke,那麼 B 線程必須等到A線程響應了傳送的消息後 才能得到返回值,而如果使用BeginInvoke,則B線程將消息送到A線程後,馬上返回,並不一定等待該消息被A線程響應完成。所以如果A線程處在繁忙狀態或休眠狀態,使用 Invoke 封送消息就會使得B線程被堵塞,而是用 BeginInvoke 則不然,這就是所謂的“異步執行”了。

空口無憑,讓我們來看看Invoke BeginInvoke 背後的代碼:

public object Invoke(Delegate method, params object[] args)
{
    using (MultithreadSafeCallScope scope = new MultithreadSafeCallScope())
    {
        return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
    }
}
 
public IAsyncResult BeginInvoke(Delegate method, params object[] args)
{
    using (MultithreadSafeCallScope scope = new MultithreadSafeCallScope())
    {
        return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);
    }
}

兩方法對應的代碼基本一樣,除了返回類型外,還有一處細微的差別 MarshaledInvoke 方法的第三個參數:Invoke  trueBeginInvoke  false。這個參數表示什麼意思呢?把MarshaledInvoke 背後的代碼拉出來看看:

private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
    int num;
    if (!this.IsHandleCreated)
    {
        throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread"));
    }
    if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null)
    {
        IntSecurity.UnmanagedCode.Demand();
    }
    bool flag = false;
    if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId()) && synchronous)
    {
        flag = true;
    }
    ExecutionContext executionContext = null;
    if (!flag)
    {
        executionContext = ExecutionContext.Capture();
    }
    ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
    lock (this)
    {
        if (this.threadCallbackList == null)
        {
            this.threadCallbackList = new Queue();
        }
    }
    lock (this.threadCallbackList)
    {
        if (threadCallbackMessage == 0)
        {
            threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
        }
        this.threadCallbackList.Enqueue(entry);
    }
    if (flag)
    {
        this.InvokeMarshaledCallbacks();
    }
    else
    {
        UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
    }
    
if (!synchronous)
    {
        return entry;
    }
    if (!entry.IsCompleted)
    {
        this.WaitForWaitHandle(entry.AsyncWaitHandle);
    }
    if (entry.exception != null)
    {
        throw entry.exception;
    }
    return entry.retVal;

}

好長的一段代碼,看着都眼暈。看看剛纔提到的第三個參數的名字 synchronous ,從字面意思看‘是否同步’,OK,再看看最後被加粗的幾行代碼(還是根據字面意思猜):如果不同步,立刻返回;如果同步,而且沒完事,就等一等,最後給出返回值。如果我們的假設成立,那麼 Invoke 方法的第三個參數爲True,就是要同步;BeginInvoke 方法的第三個參數爲 False,就是不同步,即異步。Right,到這裏就和 MSDN 幫助上介紹 BeginInvoke “異步執行”就對上了。

如果按照數學分析歸納法,到此應該說:由以上分析,得證:

Invoke 引起工作線程的阻塞,BeginInvoke 不引起工作線程的阻塞。

最後,要補充兩點:第一,如果主線程以外的工作線程要操作窗體控件,並非一定要使用Invoke方法,更改下邊這個屬性也是 OK 的,只是不推薦使用。

System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

第二,既然 BeginInvoke 不引起阻塞,那麼是否就說明它比 Invoke 好呢。我在自己的程序裏,把所有的 Invoke 均變成 BeginInvoke ,運行一段時間後,結果提示 StackOverFlowException 錯誤,幫助對這個異常的說明是“掛起的方法調用過多而導致執行堆棧溢出時引發的異常”。看來還是要慎用。

生活TMD需要激情,做事需冷靜,說話需冷靜!

遇事記着:辦法總比困難多,困難和問題說不定就是機遇和轉折!

歷史證明:哪個環節沒照顧到,哪個環節就會出問題!能自己來,就不要讓別人來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章