2010年12月21日 星期二

Invoke and BeginInvoke

在Invoke或者BeginInvoke的使用中無一例外地使用了委托Delegate。
 
一、為什麼Control類提供了Invoke和BeginInvoke機製?
關於這個問題的最主要的原因已經是dotnet程式員眾所周知的,我在此費點筆墨再次記錄到自己的日誌,以便日後提醒一下自己。

1、windows程式消息機製
Windows GUI程式是基於消息機製的,有個主線程維護著一個消息幫浦。這個消息幫浦讓windows程式生生不息。

Windows 程式有個消息隊列,窗體上的所有消息是這個隊列裡面消息的最主要來源。這裡的while循環使用了GetMessage()這個方法,這是個阻塞方法,也 就是隊列為空時方法就會被阻塞,從而這個while循環停止運動,這避免了一個程式把cpu無緣無故地耗盡,讓其它程式難以得到附應。當然在某些需要 cpu最大限度運動的程式裡面就可以使用另外的方法,例如某些3d遊戲或者及時戰略遊戲中,一般會使用PeekMessage()這個方法,它不會被 windows阻塞,從而保證整個遊戲的流暢和比較高的幀速。

這個主線程維護著整個窗體以及上面的子控件。當它得到一個消息,就會調用DispatchMessage方法派遣消息,這會引起對窗體上的視窗過程的調用。視窗過程裡面當然是程式員提供的窗體數據更新代碼和其它代碼。

2、dotnet裡面的消息循環

Code

[copy to clipboard]CODE:

public static void Main(string[] args)
{
  Form f = new Form();
  Application.Run(f);
}


Dotnet窗體程式封裝了上述的while循環,這個循環就是透過Application.Run方法啟動的。

3、線程外操作GUI控件的問題
如果從另外一個線程操作windows窗體上的控件,就會和主線程產生競爭,造成不可預料的結果,甚至死鎖。因此windows GUI編程有一個規則,就是只能透過創建控件的線程來操作控件的數據,否則就可能產生不可預料的結果。

因此,dotnet裡面,為了方便地解決這些問題,Control類實現了ISynchronizeInvoke界面,提供了Invoke和BeginInvoke方法來提供讓其它線程更新GUI界面控件的機製。

Code

[copy to clipboard]CODE:

public interface ISynchronizeInvoke
{
        [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
        IAsyncResult BeginInvoke(Delegate method, object[] args);
        object EndInvoke(IAsyncResult result);
        object Invoke(Delegate method, object[] args);
        bool InvokeRequired { get; }
}


如果從線程外操作windows窗體控件,那麼就需要使用Invoke或者BeginInvoke方法,透過一個委托把調用封送到控件所屬的線程上執行。

二、消息機製---線程間和進程間通信機製
1、window消息發送
Windows消息機製是windows平台上的線程或者進程間通信機製之一。Windows消息值其實就是定義的一個數據架構,最重要的是消息的類型,它就是一個整數;然後就是消息的參數。消息的參數可以表示很多東西。

Windows 提供了一些api用來向一個線程的消息隊列發送消息。因此,一個線程可以向另一個線程的消息隊列發送消息從而告訴對方做什麼,這樣就完成了線程間的通信。 有些api發送消息需要一個視窗句柄,這種函數可以把消息發送到指定視窗的主線程消息隊列;而有些則可以直接透過線程句柄,把消息發送到該線程消息隊列 中。
                                                


 附件: 您所在的用戶組無法下載或檢視附件
用消息機製通信

SendMessage是windows api,用來把一個消息發送到一個視窗的消息隊列。這個方法是個阻塞方法,也就是作業系統會確保消息的確發送到目的消息隊列,並且該消息被處理完畢以後,該函數才返回。返回之前,調用者將會被暫時阻塞。

PostMessage也是一個用來發送消息到視窗消息隊列的api函數,但這個方法是非阻塞的。也就是它會馬上返回,而不管消息是否真的發送到到達站,也就是調用者不會被阻塞。

2、Invoke and BeginInvoke


 附件: 您所在的用戶組無法下載或檢視附件
Invoke or BeginInvoke

Invoke 或者BeginInvoke方法都需要一個委托對象作為參數。委托類似於回調函數的位址,因此調用者透過這兩個方法就可以把需要調用的函數位址封送給界面 線程。這些方法裡面如果包含了更改控件狀態的代碼,那麼由於最終執行這個方法的是界面線程,從而避免了競爭條件,避免了不可預料的問題。如果其它線程直接 操作界面線程所屬的控件,那麼將會產生競爭條件,造成不可預料的結果。

使用Invoke完成一個委托方法的封送,就類似於使用SendMessage方法來給界面線程發送消息,是一個同步方法。也就是說在Invoke封送的方法被執行完畢前,Invoke方法不會返回,從而調用者線程將被阻塞。

使 用BeginInvoke方法封送一個委托方法,類似於使用PostMessage進行通信,這是一個異步方法。也就是該方法封送完畢後馬上返回,不會等 待委托方法的執行結束,調用者線程將不會被阻塞。但是調用者也可以使用EndInvoke方法或者其它類似WaitHandle機製等待異步操作的完成。

但是在內部實現上,Invoke和BeginInvoke都是用了PostMessage方法,從而避免了SendMessage帶來的問題。而Invoke方法的同步阻塞是靠WaitHandle機製來完成的。

3、使用場合問題
如果你的後台線程在更新一個UI控件的狀態後不需要等待,而是要繼續往下處理,那麼你就應該使用BeginInvoke來進行異步處理。

如 果你的後台線程需要操作UI控件,並且需要等到該操作執行完畢才能繼續執行,那麼你就應該使用Invoke。否則,在後台線程和主截面線程共享某些狀態數 據的情況下,如果不同步調用,而是各自繼續執行的話,可能會造成執行序列上的問題,雖然不發生死鎖,但是會出現不可預料的顯示結果或者數據處理錯誤。

可 以看到ISynchronizeInvoke有一個屬性,InvokeRequired。這個屬性就是用來在編程的時候確定,一個對象訪問UI控件的時候 是否需要使用Invoke或者BeginInvoke來進行封送。如果不需要那麼就可以直接更新。在調用者對象和UI對象同屬一個線程的時候這個屬性返回 false。在後面的代碼分析中我們可以看到,Control類對這一屬性的實現就是在判斷調用者和控件是否屬於同一個線程的。

三、Delegate.BeginInvoke
透 過一個委托來進行同步方法的異步調用,也是.net提供的異步調用機製之一。但是Delegate.BeginInvoke方法是從ThreadPool 取出一個線程來執行這個方法,以獲得異步執行效果的。也就是說,如果採用這種模式提交多個異步委托,那麼這些調用的順序無法得到保證。而且由於是使用線程 池裡面的線程來完成任務,使用頻繁,會對系統的性能造成影響。

Delegate.BeginInvoke也是講一個委托方法封送到其它線程,從而透過異步機製執行一個方法。調用者線程則可以在完成封送以後去繼續它的工作。但是這個方法封送到的最終執行線程是營運庫從ThreadPool裡面選取的一個線程。

這裡需要糾正一個誤區,那就是Control類上的異步調用BeginInvoke並沒有開辟新的線程完成委托任務,而是讓界面控件的所屬線程完成委托任務的。看來異步操作就是開辟新線程的說法不一定準確。

四、用Reflector察看一些相關代碼
1、Control.BeginInvoke and Control.Invoke

Code

[copy to clipboard]CODE:

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


這 裡的FindMarshalingControl方法透過一個循環向上回溯,從當前控件開始回溯父控件,直到找到最頂級的父控件,用它作為封送對象。例 如,我們調用窗體上一個進度條的Invoke方法封送委托,但是實際上會回溯到主窗體,透過這個控件對象來封送委托。因為主窗體是主線程消息隊列相關的, 發送給主窗體的消息才能發送到界面主線程消息隊列。

我們可以看到Invoke和BeginInvoke方法使用了同樣的實現,只是MarshaledInvoke方法的最後一個參數值不一樣。

2、MarshaledInvoke

Code

[copy to clipboard]CODE:

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
    {            //終於找到你了,PostMessage
        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;
}


怎么樣,我們終於看到PostMessage了吧?透過windows消息機製實現了封送。而需要封送的委托方法作為消息的參數進行了傳遞。關於其它的代碼這裡不作進一步解釋。

3、InvokeRequired

Code

[copy to clipboard]CODE:

public bool InvokeRequired
{
    get
    {
        using (new MultithreadSafeCallScope())
        {
            HandleRef ref2;
            int num;
            if (this.IsHandleCreated)
            {
                ref2 = new HandleRef(this, this.Handle);
            }
            else
            {
                Control wrapper = this.FindMarshalingControl();
                if (!wrapper.IsHandleCreated)
                {
                    return false;
                }
                ref2 = new HandleRef(wrapper, wrapper.Handle);
            }
            int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
            int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
            return (windowThreadProcessId != currentThreadId);
        }
    }
}


終於看到了,這是在判斷windows窗體線程和當前的調用者線程是否是同一個,如果是同一個就沒有必要封送了,直接訪問這個GUI控件吧。否則,就不要那麼直接表白了,就需要Invoke或者BeginInvoke做媒了。

出處/博客園

interface和abstract class有何不同? (C/C++) (.NET) (C#)

Abstract
這兩個的確非常的像,主要都是為了實踐『多型』,但實際的用途並不一樣。

Introduction
interface和abstract class在語言層次的差異,我就不再贅述,本文主要是放在何時該使用interface?何時該使用abstract class?

interface用在當一個物件須和其他物件共同合作時,為了確保其他物件有我想要的method,所以定下interface要該物件遵守,在Design Pattern到處可以看到這種應用,如strategy,bridge,prototype...。

而abstract class是用在整個繼承體系的最上層,用來定義出整個繼承體系該有哪些method,子類別可以對這些method加以override,或維持和 abstract class相同的功能。Design Pattern中的template method,factory method...等就是用這種手法。

或者更明白的說,我們知道在OO主要有兩種技術:繼承(Inheritance)和組合(Composition),而abstract class就是用在使用繼承技術時,而interface則是用在使用組合技術時。

使用繼承技術時,我們會將所有method由abstract class去宣告,然後由各子類別去override,若不得已某些class有自己的特殊method,則由該class自行宣告。

一旦使用組合時時,就牽涉到一個問題,你如何確保被你組合的物件有某個method呢?當你使用繼承時,因為所有的method都會被繼承,這不是問題, 但組合就不一樣了,所以你必須建立一個interface,強迫要被你組合的物件,需實做這個interface,這樣當你要使用該物件時,才能確保有某 個method可以呼叫。

Sample Code
以Door為例,Door是一個泛稱,適合當abstract class,定義出open()和close(),由於一般們都是水平左右開,所以可以將左右開的功能放在abstract class,今天有一個垂直上下開的門VerticalDoor,門是水平開的,明顯和abstract class不一樣,所以使用了override的方式去改寫,在此範例我們使用了『繼承』的技術。

UML



C++

 1/**//*
 2(C) OOMusou 2007 http://oomusou.cnblogs.com
 3
 4Filename    : DoorInheritance.cpp
 5Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
 6Description : Demo how to use abstract class
 7Release     : 05/07/2007 1.0
 8*/

 9
10#include <iostream>
11#include <vector>
12#include <algorithm>
13#include <functional>
14
15using namespace std;
16
17class Door {
18public:
19  virtual void open() const {
20    cout << "open horizontally" << endl;
21  }

22 
23  virtual void close() const {
24    cout << "close horizontally" << endl;
25  }

26}
;
27
28class HorizontalDoor : public Door {
29}
;
30
31class VerticalDoor : public Door {
32public:
33  void open() const {
34    cout << "open vertically" << endl;
35  }

36 
37  void close() const {
38    cout << "close vertically" << endl;
39  }

40}
;
41
42class DoorController {
43protected:
44  vector<Door*> _doorVec;
45 
46public:
47  void addDoor(Door& aDoor) {
48    _doorVec.push_back(&aDoor);
49  }

50 
51  void openDoor() const {
52    for_each(_doorVec.begin(), _doorVec.end(), mem_fun(&Door::open));
53  }

54}
;
55
56
57int main() {
58  DoorController dc;
59  dc.addDoor(HorizontalDoor());
60  dc.addDoor(VerticalDoor());
61  dc.openDoor();
62}

C#

 1/**//*
 2(C) OOMusou 2007 http://oomusou.cnblogs.com
 3
 4Filename    : DoorInheritance.cs
 5Compiler    : Visual Studio 2005 / C# 2.0
 6Description : Demo how to use abstract class
 7Release     : 05/07/2007 1.0
 8*/

 9using System;
10using System.Collections.Generic;
11
12class Door {
13  public virtual void open() {
14    Console.WriteLine("open horizontally");
15  }

16 
17  public virtual void close() {
18    Console.WriteLine("close horizontally");
19  }

20}

21
22class HorizontalDoor : Door {
23}

24
25class VerticalDoor : Door {
26  public override void open() {
27    Console.WriteLine("open vertically");
28  }

29 
30  public override void close() {
31    Console.WriteLine("close vertically");
32  }

33}

34
35class DoorController {
36  protected List<Door> _doorList = new List<Door>();
37 
38  public void addDoor(Door aDoor) {
39    _doorList.Add(aDoor);
40  }

41 
42  public void openDoor() {
43    foreach(Door iter in _doorList) {
44      iter.open();
45    }

46  }

47}

48
49class main {
50  public static void Main() {
51    DoorController dc = new DoorController();
52    dc.addDoor(new HorizontalDoor());
53    dc.addDoor(new VerticalDoor());
54    dc.openDoor();
55  }

56}

執行結果

open horizontally
open vertically

假如日後需求改變,需要一個會警報的門AlarmDoor,由於廠商本身沒有生產警報器,所以勢必外包,這時他將規格定義成IAlarm interface,只要能生產出這個規格的Alarm,就可以透過『組合』的方式產生出會警報的門,在此範例我們使用『組合』技術。

UML(以下這個圖我畫錯了,_alarm應該在AlarmDoor內,感謝frank28_nfls的指正,C++與C#的code是對的)




C++

 1/**//*
 2(C) OOMusou 2007 http://oomusou.cnblogs.com
 3
 4Filename    : DoorInheritance.cpp
 5Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
 6Description : Demo how to use interface
 7Release     : 05/07/2007 1.0
 8*/

 9
10#include <iostream>
11#include <vector>
12#include <algorithm>
13#include <functional>
14
15using namespace std;
16
17class Door {
18public:
19  virtual void open() const {
20    cout << "open horizontally" << endl;
21  }

22 
23  virtual void close() const {
24    cout << "close horizontally" << endl;
25  }

26}
;
27
28class HorizontalDoor : public Door {
29}
;
30
31class VerticalDoor : public Door {
32public:
33  void open() const {
34    cout << "open vertically" << endl;
35  }

36 
37  void close() const {
38    cout << "close vertically" << endl;
39  }

40}
;
41
42class IAlarm {
43public:
44  virtual void alert() const = 0;
45}
;
46
47class Alarm : public IAlarm {
48public:
49  void alert() const {
50    cout << "ring,ring,ring" << endl;
51  }

52}
;
53
54class AlarmDoor : public Door {
55protected:
56  IAlarm* _alarm;
57 
58public:
59  AlarmDoor() {
60    _alarm = new Alarm;
61  }

62 
63  ~AlarmDoor() {
64    delete _alarm;
65  }

66
67public:
68  void alert() {
69    _alarm->alert();
70  }

71}
;
72
73class DoorController {
74protected:
75  vector<Door*> _doorVec;
76 
77public:
78  void addDoor(Door& aDoor) {
79    _doorVec.push_back(&aDoor);
80  }

81 
82  void openDoor() {
83    for_each(_doorVec.begin(), _doorVec.end(), mem_fun(&Door::open));
84  }

85}
;
86
87int main() {
88  DoorController dc;
89  dc.addDoor(HorizontalDoor());
90  dc.addDoor(VerticalDoor());
91  dc.addDoor(AlarmDoor());
92  dc.openDoor();
93 
94  Door& door = AlarmDoor();
95  dynamic_cast<AlarmDoor&>(door).alert();
96}

C#

 1/**//*
 2(C) OOMusou 2007 http://oomusou.cnblogs.com
 3
 4Filename    : DoorInheritance.cs
 5Compiler    : Visual Studio 2005 / C# 2.0
 6Description : Demo how to use interface
 7Release     : 05/07/2007 1.0
 8*/

 9
10using System;
11using System.Collections.Generic;
12
13abstract class Door {
14  public virtual void open() {
15    Console.WriteLine("open horizontally");
16  }

17 
18  public virtual void close() {
19    Console.WriteLine("close horizontally");
20  }

21}

22
23class HorizontalDoor : Door {
24}

25
26class VerticalDoor : Door {
27  override public void open() {
28    Console.WriteLine("open vertically");
29  }

30 
31  override public void close() {
32    Console.WriteLine("close vertically");
33  }

34}

35
36interface IAlarm {
37  void alert();
38}

39
40class Alarm : IAlarm {
41  public void alert() {
42    Console.WriteLine("ring,ring,ring");
43  }

44}

45
46class AlarmDoor : Door {
47  private IAlarm _alarm;
48 
49  public AlarmDoor() {
50    _alarm = new Alarm();
51  }

52
53  public void alert() {
54    _alarm.alert();
55  }

56}

57
58class DoorController {
59  protected List<Door> _doorList = new List<Door>();
60
61  public void addDoor(Door aDoor) {
62    _doorList.Add(aDoor);
63  }

64
65  public void openDoor() {
66    foreach (Door iter in _doorList) {
67      iter.open();
68    }

69  }

70}

71
72class main {
73  public static void Main() {
74    DoorController dc = new DoorController();
75    dc.addDoor(new HorizontalDoor());
76    dc.addDoor(new VerticalDoor());
77    dc.addDoor(new AlarmDoor());
78    dc.openDoor();
79 
80    Door door = new AlarmDoor();
81    ((AlarmDoor)(door)).alert();
82  }

83}

執行結果

open horizontally
open vertically
open horizontally
ring
,ring,ring

值得注意的是,我並沒有將Door()這個abstract class加上alert(),因為alert()並非所有門都有,所以不應該放在abstract class,另外abstract class也不該隨意更改,所以才會說,abstract class和interface在設計時非常重要,整個多型的技術都是靠interface和abstract class支撐,只要interface或abstract class一變,整個多型機制就瓦解了。

Conclusion
interface和abstract class的差異,重點是在用的地方完全不同,而非僅是語法上的小差異,若從語言差異的角度去看interface和abstract class,當然會搞的一頭霧水,若從OO和Design Pattern的角度去看,才能較容易分辨interface和abstract的異同。

From:http://www.cnblogs.com/oomusou/archive/2007/05/07/738311.html