關於這些疑問,我們將會透過底下的測試程式,來進行說明。
測試程式
public class Worker
{
public delegate void MyHandler(object sender, EventArgs args);
public MyHandler MyDelegateHandler;
public event MyHandler MyDelegateEventHandler;
public event EventHandler MyEventHandler;
public void Dowork()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Dowork {i}");
OnWorkByEvent(i);
OnWorkByDelegate(i);
OnWorkByDelegateEvent(i);
}
}
private void OnWorkByEvent(int i)
{
var fooHandler = MyEventHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByEvent raise {i}");
fooHandler(this, EventArgs.Empty);
}
}
private void OnWorkByDelegate(int i)
{
var fooHandler = MyDelegateHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByDelegate raise {i}");
fooHandler(this, EventArgs.Empty);
}
}
private void OnWorkByDelegateEvent(int i)
{
var fooHandler = MyDelegateEventHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByDelegateEvent raise {i}");
fooHandler(this, EventArgs.Empty);
}
}
}
class Program
{
public static Worker fooWorker = new Worker();
public static Random Rm = new Random(DateTime.Now.Millisecond);
static void Main(string[] args)
{
fooWorker.MyDelegateHandler += MyDelegateListener;
fooWorker.MyEventHandler += MyDelegateListener;
fooWorker.MyDelegateEventHandler += MyDelegateListener;
fooWorker.Dowork();
Console.ReadKey();
}
private static void MyDelegateListener(object sender, EventArgs args)
{
var foo = Rm.Next(500, 2000);
Console.WriteLine($"Sleep {foo}");
Thread.Sleep(Rm.Next(500, 2000));
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
}
}
底下是這個範例程式的執行結果
Dowork 0
OnWorkByEvent raise 0
Sleep 1930
1
OnWorkByDelegate raise 0
Sleep 882
1
OnWorkByDelegateEvent raise 0
Sleep 1169
1
Dowork 1
OnWorkByEvent raise 1
Sleep 579
1
OnWorkByDelegate raise 1
Sleep 1355
1
OnWorkByDelegateEvent raise 1
Sleep 1734
1
Dowork 2
OnWorkByEvent raise 2
Sleep 1570
1
OnWorkByDelegate raise 2
Sleep 1035
1
OnWorkByDelegateEvent raise 2
Sleep 539
1
Dowork 3
OnWorkByEvent raise 3
Sleep 642
1
OnWorkByDelegate raise 3
Sleep 1499
1
OnWorkByDelegateEvent raise 3
Sleep 1151
1
Dowork 4
OnWorkByEvent raise 4
Sleep 651
1
OnWorkByDelegate raise 4
Sleep 1163
1
OnWorkByDelegateEvent raise 4
Sleep 1536
1
解釋說明
在測試程式中,我們產生一個
Worker
類別,在這個類別中,宣告了- MyDelegateHandler這是一個委派物件
- MyDelegateEventHandler這是一個事件,使用
event
來宣告,不過,使用一個delegate
委派來宣告該事件的事件方法。 - MyEventHandler這是一個事件,使用
event
來宣告,不過,使用的是EventHandler
這個類別,來宣告該事件的事件方法。
關於這三者的差異點在於,MyDelegateEventHandler & MyEventHandler 的這兩種做法,其實是一樣的,因為
EventHandler
的定義是:public delegate void EventHandler(object sender, EventArgs e);
因此,
EventHandler
其實就是一個委派的類別宣告,這與我們自己宣告的 MyHandler
其實表示的都是一樣的。因此,MyDelegateEventHandler
& MyEventHandler
作法與結果都是相同的。
不論是否有使用
event
關鍵字,當使用了 +=
運算子要把訂閱事件加入的時候,我們從下圖看到,這個時候 MyDelegateHandler
這個物件當時是 null
空值,不過,因為使用了 +=
運算子,卻是可以把 MyDelegateListener
監聽事件方法加入進來。
當繼續執行下一行程式碼,並不會因為
MyDelegateHandler
為空值,就產生了例外異常,我們可以從下圖看到,這個時候,MyDelegateHandler
已經有值了。
最後,對於當我們在執行
Dowork
方法的時候,我們會判斷這些事件或者委派物件是否為空值,若不為空值,則就會依序執行這些訂閱的方法;不過,您會看到,這是採用同步的執行方式,若某次的監聽事件訂閱方法執行過久,例如,需要10分鐘的時間, Dowork
的下一次迴圈動作,將也會延遲10分鐘。
若您不想要這麼處理,您可以使用多執行續的方式,讓這些監聽訂閱事件可以非同步的執行,這樣,
Dowrk
的迴圈就可以很快速地繼續執行下去。
想要做到這樣,我們可以使用
ThreadPool.QueueUserWorkItem
來啟動多執行續的運行,而 ThreadPool.QueueUserWorkItem
方法需要一個 WaitCallback
委派物件,其定義如下:public delegate void WaitCallback(object state);
也就是說,
WaitCallback
這個委派物件,需要一個可以傳入一個引數的方法,因此,我們使用底下的方式來啟動多執行續來運行。 ThreadPool.QueueUserWorkItem(s =>
{
fooHandler(this, EventArgs.Empty);
});
最後,請將
Worker
這個類別修改成為底下的定義,當要執行這些訂閱事件的時候,就是使用不同執行續來執行。 public class Worker
{
public delegate void MyHandler(object sender, EventArgs args);
public MyHandler MyDelegateHandler;
public event MyHandler MyDelegateEventHandler;
public event EventHandler MyEventHandler;
public void Dowork()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Dowork {i}");
OnWorkByEvent(i);
OnWorkByDelegate(i);
OnWorkByDelegateEvent(i);
}
}
private void OnWorkByEvent(int i)
{
var fooHandler = MyEventHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByEvent raise {i}");
//fooHandler(this, EventArgs.Empty);
ThreadPool.QueueUserWorkItem(s =>
{
fooHandler(this, EventArgs.Empty);
});
}
}
private void OnWorkByDelegate(int i)
{
var fooHandler = MyDelegateHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByDelegate raise {i}");
//fooHandler(this, EventArgs.Empty);
ThreadPool.QueueUserWorkItem(s =>
{
fooHandler(this, EventArgs.Empty);
});
}
}
private void OnWorkByDelegateEvent(int i)
{
var fooHandler = MyDelegateEventHandler;
if (fooHandler != null)
{
Console.WriteLine($"OnWorkByDelegateEvent raise {i}");
//fooHandler(this, EventArgs.Empty);
ThreadPool.QueueUserWorkItem(s =>
{
fooHandler(this, EventArgs.Empty);
});
}
}
}
class Program
{
public static Worker fooWorker = new Worker();
public static Random Rm = new Random(DateTime.Now.Millisecond);
static void Main(string[] args)
{
fooWorker.MyDelegateHandler += MyDelegateListener;
fooWorker.MyEventHandler += MyDelegateListener;
fooWorker.MyDelegateEventHandler += MyDelegateListener;
fooWorker.Dowork();
Console.WriteLine("Please Wait...");
Console.ReadKey();
}
private static void MyDelegateListener(object sender, EventArgs args)
{
var foo = Rm.Next(500, 2000);
Console.WriteLine($"Sleep {foo}");
Thread.Sleep(Rm.Next(500, 2000));
Console.WriteLine($"Thread ID is : {Thread.CurrentThread.ManagedThreadId}");
}
}
此時,執行結果如下,我們可以看到,
Dowork
很快就執行完畢,不過,訂閱的事件,卻是非同步的執行,因為每個訂閱監聽事件要停留的時間都不一致,因此,結束執行時間也不太一樣。Dowork 0
OnWorkByEvent raise 0
OnWorkByDelegate raise 0
OnWorkByDelegateEvent raise 0
Dowork 1
OnWorkByEvent raise 1
OnWorkByDelegate raise 1
OnWorkByDelegateEvent raise 1
Dowork 2
OnWorkByEvent raise 2
OnWorkByDelegate raise 2
Sleep 1240
Sleep 667
Sleep 797
Sleep 1124
Sleep 1441
Sleep 583
Sleep 1060
OnWorkByDelegateEvent raise 2
Dowork 3
OnWorkByEvent raise 3
OnWorkByDelegate raise 3
OnWorkByDelegateEvent raise 3
Dowork 4
OnWorkByEvent raise 4
OnWorkByDelegate raise 4
OnWorkByDelegateEvent raise 4
Please Wait...
Sleep 769
Thread ID is : 8
Sleep 1224
Thread ID is : 10
Sleep 837
Thread ID is : 5
Sleep 708
Thread ID is : 8
Sleep 1077
Thread ID is : 6
Sleep 747
Thread ID is : 5
Sleep 1061
Thread ID is : 3
Sleep 1925
Thread ID is : 4
Thread ID is : 10
Thread ID is : 9
Thread ID is : 7
Thread ID is : 3
Thread ID is : 6
Thread ID is : 8
Thread ID is : 5
了解更多關於 [使用委派] 的使用方式
了解更多關於 [使用事件] 的使用方式了解更多關於 [C# 程式設計手冊]
event
這個關鍵字或者delegate
來實踐,不過,這兩者在當要訂閱事件的時候,卻有著不同的用法與注意事項;另外,當要執行訂閱事件的方法時候,究竟是採用多執行續的方式,也就是可以同時執行多個訂閱事件方法,還是採用同步的方式來執行?了解更多關於 [C# 程式設計手冊]