上一篇文章(
什么是.Net的異步機制(委托Delegate) - step 1)
中,
我已經(jīng)解釋了什么是異步編程,
那么現在我們就開(kāi)始具體的說(shuō)怎樣異步編程.
我們怎樣進(jìn)行異步編程/開(kāi)發(fā)?
現在擴充下上篇文章的類(lèi)(AsyncTest),提供更多的例子并從中做下簡(jiǎn)單的對比, 從新的認識下異步的內部機制,下面我們增加一個(gè)新的委托
1步,我們添加一個(gè)新方法(計算年薪YearlySalary)
public decimal YearlySalary(decimal salary, int monthCount, decimal bonus);
2步,為這個(gè)方法增加異步的功能,這樣我們仍然使用委托(Delegate)
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);
經(jīng)過(guò)簡(jiǎn)單修改后,下面是我們新的AsyncTest類(lèi)
Code1
1//我們使用委托來(lái)提供.Net的異步機制
2public delegate string AsyncEventHandler(string name); // 對應Hello 方法
3public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 對應YearlySalary方法
4public class AsyncTest
5{
6 public string Hello(string name)
7 {
8 return "Hello:" + name;
9 }
10
11 /**//// <summary>
12 /// 計算一年的薪水
13 /// </summary>
14 /// <param name="salary">月薪</param>
15 /// <param name="monthCount">一年支付月數量</param>
16 /// <param name="bonus">獎金</param>
17 /// <returns></returns>
18 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
19 {
20 //添加輔助方法,查看當前的線(xiàn)程ID
21 Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
22
23 return salary * monthCount + bonus;
24 }
25}
這里用.NET Reflector 5 來(lái)反編譯,之所以用這個(gè),因為比微軟的會(huì )更加清晰明了.如果想了解這個(gè)工具的朋友可查看(http://reflector.red-gate.com/)
圖1
開(kāi)始我先對圖1中的小圖標進(jìn)行個(gè)簡(jiǎn)單的解釋
=
類(lèi)(Class)
= 類(lèi)繼承的基類(lèi) = sealed(
委托)
=
類(lèi)的構造函數 = 方法 =
virtual方法
下面我們先比較下SalaryEventHandler與 AsyncEventHandler委托的異同.
1) SalaryEventHandler
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);
圖2.1
編譯器生成的類(lèi)Code2.1(圖2.1)
Code 2.1
1 public sealed class SalaryEventHandler : MulticastDelegate
2 {
3 public SalaryEventHandler(object @object, IntPtr method)
4 {.}
5 public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus,
AsyncCallback callback, object @object)
6 {}
7 public virtual decimal EndInvoke(IAsyncResult result)
8 {}
9 public virtual decimal Invoke(decimal salary, int monthCount, decimal bonus)
10 {}
11 } 2) AsyncEventHandler
public delegate string AsyncEventHandler(string name);
圖2.2
編譯器生成的類(lèi)Code2.2(圖2.2)
Code2.2
1 public sealed class AsyncEventHandler : MulticastDelegate
2 {
3 public AsyncEventHandler(object @object, IntPtr method)
4 {.}
5 public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
6 {}
7 public virtual string EndInvoke(IAsyncResult result)
8 {}
9 public virtual string Invoke(string name)
10 {}
11 }
對比兩個(gè)委托(事實(shí)上是一個(gè)sealed 的類(lèi)),都繼承于System.MuliticaseDelegate, 三個(gè)virtual的 Invoke / BeginInvoke / EndInvoke 方法.
//同步方法
Invoke : 參數的個(gè)數,類(lèi)型, 返回值都不相同
//異步方法,作為一組來(lái)說(shuō)明
BeginInvoke : 參數的個(gè)數和類(lèi)型不同,返回值相同
EndInvoke : 參數相同,返回值不同
這里我們先介紹下 Invoke這個(gè)方法, 我們用SalaryEventHandler委托為例(直接調用Code 1 的類(lèi))
Code 3
1class Program
2{
3 static void Main(string[] args)
4 {
5 //添加輔助方法,查看當前的線(xiàn)程ID
6 Console.WriteLine("Main Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
7
8 AsyncTest test = new AsyncTest();
9 //[1],我們習慣的調用方式
10 decimal v1 = test.YearlySalary(100000, 15, 100000);
11 //使用委托調用
12 SalaryEventHandler salaryDelegate = test.YearlySalary;
13 //[2],編譯器會(huì )自動(dòng)的把[2]轉變成[3]Invoke的調用方式,[2]和[3]是完全相同的
14 decimal v2 = salaryDelegate(100000, 15, 100000);
15 //[3]
16 decimal v3 = salaryDelegate.Invoke(100000, 15, 100000);
17
18 Console.WriteLine("V1:{0},V2:{1},V3:{2}", v1, v2, v3);
19 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
20 }
21} 輸出的結果
圖3
從結果可以看出,他們是同一個(gè)線(xiàn)程調用的(都是#10).這就說(shuō)明[1],[2],[3]是同步調用
[2],[3]對比[1], 只不過(guò)[2],[3]是通過(guò)委托的方式(其實(shí)我們可以說(shuō)成“通過(guò)代理的方式完成”),[1]是直接的調用.舉一個(gè)我們平常生活中例子:買(mǎi)機票,我們到代理點(diǎn)購買(mǎi)機票而不是直接跑到機場(chǎng)購買(mǎi),就好像我們叫別人幫我們買(mǎi)機票一樣,最后到手的機票是一樣的, SalaryEventHandler就是我們的代理點(diǎn).所以用”代理”的方式還是直接調用的方式,他們提供的參數和返回值必須是一樣的.
接下來(lái)我們開(kāi)始講異步機制核心的兩個(gè)方法BeginInvoke/EndInvoke,他們作為一個(gè)整體來(lái)完成Invoke方法的調用,不同于Inoke方法的是他們是異步執行(另外開(kāi)一個(gè)線(xiàn)程執行)的,下面先解釋下他們的作用
BeginInvoke : 開(kāi)始一個(gè)異步的請求,調用線(xiàn)程池中一個(gè)線(xiàn)程來(lái)執行
EndInvoke : 完成異步的調用, 處理返回值 和 異常錯誤.
注意: BeginInvoke和EndInvoke必須成對調用.即使不需要返回值,但EndInvoke還是必須調用,否則可能會(huì )造成內存泄漏.
我們來(lái)對比下 SalaryEventHandler與 AsyncEventHandler委托反編譯BeginInoke后的異同.
SalaryEventHandler 委托:
public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus, AsyncCallback callback, object @object)
AsyncEventHandler 委托:
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
可以看出參數的個(gè)數和類(lèi)型是不同的,我們把焦點(diǎn)放到他們的相同點(diǎn)上,
1,返回值是相同的: 返回IAsyncResult 對象(異步的核心). IAsyncResult是什么呢? 簡(jiǎn)單的說(shuō),他存儲異步操作的狀態(tài)信息的一個(gè)接口,也可以用他來(lái)結束當前異步.具體的可以看下 http://msdn.microsoft.com/zh-cn/library/system.iasyncresult(VS.80).aspx
2,編譯器會(huì )根據委托的參數個(gè)數和類(lèi)型生成相應的BeginInvoke方法,只有最后兩個(gè)參數是永遠相同的,他提供一個(gè)AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) 和一個(gè) Object 對象.
我們再來(lái)看看EndInvoke的異同.
SalaryEventHandler 委托:
public virtual decimal EndInvoke(IAsyncResult result)
AsyncEventHandler 委托:
public virtual string EndInvoke(IAsyncResult result)
EndInvoke的參數是一樣的, 唯一是在是返回值不同(他們會(huì )根據自己委托的返回值類(lèi)型生成自己的類(lèi)型)
好,下面我會(huì )通過(guò)例子來(lái)說(shuō)明BeginInvoke/EndInvoke,還是使用SalaryEventHandler委托為例(直接調用Code 1 的類(lèi))
.Net Framework 提供了兩種方式來(lái)使用異步方法
第一種: 通過(guò)IAsyncResult 對象
Code 4.1
1class Program
2{
3 static IAsyncResult asyncResult;
4
5 static void Main(string[] args)
6 {
7
8 AsyncTest test = new AsyncTest();
9 SalaryEventHandler dele = test.YearlySalary;
10 //異步方法開(kāi)始執行,返回IAsyncResult(存儲異常操作的狀態(tài)信息) 接口,同時(shí)EndInvoke 方法也需要他來(lái)作為參數來(lái)結束異步調用
11 asyncResult = dele.BeginInvoke(100000, 15, 100000, null, null);
12 //獲取返回值
13 decimal val = GetResult();
14 Console.WriteLine(val);
15 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
16 }
17
18 static decimal GetResult()
19 {
20 decimal val = 0;
21 //獲取原始的委托對象:先是獲取AsyncResult對象,再根據他的AsyncDelegate屬性來(lái)調用當前的(那一個(gè))委托對象
22 AsyncResult result = (AsyncResult)asyncResult;
23 SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24
25 //調用EndInvoke獲取返回值
26 val = salDel.EndInvoke(asyncResult);
27
28 return val;
29 }
30} 第二種: 通過(guò)回調函數. 使用倒數第二個(gè)參數AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) ,建議使用這種方法.
Code 4.2
1class Program
2{
3 static void Main(string[] args)
4 {
5 AsyncTest test = new AsyncTest();
6 SalaryEventHandler dele = test.YearlySalary;
7
8 //異步方法開(kāi)始執行,使用BeginInvoke 倒數第二個(gè)參數(AsyncCallback委托對象) ,而不用返回值
9 dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, null);
10 //和上面相同的
11 //AsyncCallback callback = new AsyncCallback(GetResultCallBack);
12 //dele.BeginInvoke(100000, 15, 100000, callback, null);
13
14 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
15 }
16
17 //必須遵循AsyncCallback 委托的定義:返回值為空,一個(gè)IAsyncResult對象參數
18 static void GetResultCallBack(IAsyncResult asyncResult)
19 {
20 decimal val = 0;
21 //獲取原始的委托對象
22 AsyncResult result = (AsyncResult)asyncResult;
23 SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24
25 //調用EndInvoke獲取返回值
26 val = salDel.EndInvoke(asyncResult);
27
28 Console.WriteLine(val);
29 }
30}
BeginInvoke最后一個(gè)參數是做什么的呢?我把Code 4.2 方法修改下.
Code 4.3
1class Program
2{
3 static void Main(string[] args)
4 {
5 AsyncTest test = new AsyncTest();
6 SalaryEventHandler dele = test.YearlySalary;
7
8 //異步方法開(kāi)始執行,看最后一個(gè)參數(Object對象) [Note1:],這里我們傳遞2000(int)
9 dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, 2000);
10
11 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
12 }
13
14 static void GetResultCallBack(IAsyncResult asyncResult)
15 {
16 //[Note1:],他的作用就是來(lái) "傳遞額外的參數",因為他本身是Object對象,我們可以傳遞任何對象
17 int para = (int)asyncResult.AsyncState;
18 Console.WriteLine(para);//輸出:2000
19 }
20}
異步的異常處理
接下來(lái)再講講EndInvoke,獲取最后的返回值之外,他的一個(gè)重要的應用在”引發(fā)異常來(lái)從異步操作返回異常”
Code 5
1class Program
2{
3 static void Main(string[] args)
4 {
5 AsyncTest test = new AsyncTest();
6 SalaryEventHandler dele = test.YearlySalary;
7
8 dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, null);
9 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
10 }
11
12 static void GetResultCallBack(IAsyncResult asyncResult)
13 {
14 decimal val = 0;
15 //獲取原始的委托對象
16 AsyncResult result = (AsyncResult)asyncResult;
17 SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
18 try
19 {
20 //如果EndInvoke發(fā)生異常,會(huì )在EndInvoke得到原始的異常.
21 val = salDel.EndInvoke(asyncResult);
22 Console.WriteLine(val);
23 }
24 catch (Exception ex)
25 {
26 Console.WriteLine(ex.Message);
27 }
28 }
29}
30public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 對應YearlySalary方法
31public class AsyncTest
32{
33 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
34 {
35 throw new Exception("error"); //引發(fā)異常
36 return salary * monthCount + bonus;
37 }
38}
我們主動(dòng)在YearlySalary方法中引發(fā)異常,BeginInvoke開(kāi)始異步調用的時(shí)候捕獲到了這個(gè)異常,.Net Framework會(huì )在EndInvoke得到原始的異常.
說(shuō)到這里,大家是否可以簡(jiǎn)單的應用委托來(lái)開(kāi)始自己的異步操作呢? 下面看看我是怎樣為我自己的類(lèi)添加異步的.
第1步, 類(lèi)的定義,需要遵循.Net Framework 的規則
1)同步和異步是同時(shí)并存的
2)從最上面的兩個(gè)委托SalaryEventHandler與 AsyncEventHandler生成的BeginInvoke / EndInvoke 對比中看出,我們也來(lái)定義我們自己的異步方法,我們遵循微軟設計師異步方法設計的規則,Begin+同步方法名 / End+同步方法名
BeginXXX 必須返回IAsyncResult對象,后兩位參數必須為AsyncCallback callback, object state,前面的參數和同步方法的參數一樣
EndXXX 參數必須為IAsyncResult對象,返回值為同步方法的返回值
Code 6.1
1public class AsyncTest
2{
3 private delegate string AsyncEventHandler(string name);
4 private AsyncEventHandler _Async;
5 public string Hello(string name)
6 {
7 return "Hello:" + name;
8 }
9
10 //按照.Net Framework的規則 ,編寫(xiě)我們自己的BeginInvoke方法
11 public virtual IAsyncResult BeginHello(string name, AsyncCallback callback, object state)
12 {
13 AsyncEventHandler del = Hello;
14
15 this._Async = del;
16
17 return del.BeginInvoke(name, callback, state);
18 }
19 //編寫(xiě)我們自己的EndInvoke方法
20 public virtual string EndHello(IAsyncResult asyncResult)
21 {
22 if (asyncResult == null)
23 throw new ArgumentNullException("asyncResult");
24 if (this._Async == null)
25 throw new ArgumentException("_Async");
26
27 string val = string.Empty;
28 try
29 {
30 val = this._Async.EndInvoke(asyncResult);
31 }
32 finally
33 {
34 this._Async = null;
35 }
36 return val;
37 }
38}
第2步: 調用我們編寫(xiě)的類(lèi)
Code 6.2
1class Program
2{
3 static void Main(string[] args)
4 {
5 AsyncTest test = new AsyncTest();
6 //使用回調函數,就是上面提到的"第二種"
7 AsyncCallback callback = new AsyncCallback(OnHelloCallback);
8 test.BeginHello("Andy Huang", callback, test);
9 //和上面一樣
10 //IAsyncResult result = test.BeginHello("Andy Huang", OnHelloCallback, test);
11
12 Console.ReadLine(); // 讓黑屏等待,不會(huì )直接關(guān)閉..
13 }
14
15 static void OnHelloCallback(IAsyncResult asyncResult)
16 {
17 //獲取額外的參數
18 AsyncTest obj = (AsyncTest)asyncResult.AsyncState;
19 string val = obj.EndHello(asyncResult);
20 Console.WriteLine(val);
21 }
22}
附:Hello的方法的異步重構在下面的代碼中可以下載到.
下一篇中我們會(huì )說(shuō)說(shuō)異步中的一些高級應用,異步的核心,還有微軟.Net Framework 為我們提供的各種類(lèi)中(具有異步方法的類(lèi)),我們是怎么使用這些方法的.最后要提醒下大家:濫用異步,會(huì )影響性能,而且增加編程難度
以上有word 文檔直接粘貼,排版可能不太看,你可以通過(guò)下面來(lái)下載相應的代碼/文檔
1,文檔
2,代碼 (VS2008開(kāi)發(fā),.Net Framework 2.0(C Sharp)編寫(xiě))