程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 什麼是.Net的異步機制(Invoke,BeginInvoke,EndInvoke) - step 2

什麼是.Net的異步機制(Invoke,BeginInvoke,EndInvoke) - step 2

編輯:關於.NET

我們怎樣進行異步編程/開發?

現在擴充下上篇文章的類(AsyncTest),提供更多的例子並從中做下簡單的對比, 從新的認識下異步的內部機制,下面我們增加一個新的委托

1步,我們添加一個新方法(計算年薪YearlySalary)

public decimal YearlySalary(decimal salary, int monthCount, decimal bonus);

2步,為這個方法增加異步的功能,這樣我們仍然使用委托(Delegate)

public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);

經過簡單修改後,下面是我們新的AsyncTest類

Code1

1//我們使用委托來提供.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    //添加輔助方法,查看當前的線程ID
21    Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
22
23    return salary * monthCount + bonus;
24  }
25}

這裡用.NET Reflector 5 來反編譯,之所以用這個,因為比微軟的會更加清晰明了.如果想了解這個工具的朋友可查看(http://reflector.red-gate.com/)

圖1

開始我先對圖1中的小圖標進行個簡單的解釋

= 類(Class)  = 類繼承的基類 = sealed(委托)

= 類的構造函數 = 方法 =virtual方法

下面我們先比較下SalaryEventHandler與 AsyncEventHandler委托的異同.

1)   SalaryEventHandler

public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);

圖2.1

編譯器生成的類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

編譯器生成的類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  }

對比兩個委托(事實上是一個sealed 的類),都繼承於System.MuliticaseDelegate, 三個virtual的 Invoke / BeginInvoke / EndInvoke 方法.

//同步方法

Invoke : 參數的個數,類型, 返回值都不相同

//異步方法,作為一組來說明

BeginInvoke : 參數的個數和類型不同,返回值相同

EndInvoke : 參數相同,返回值不同

這裡我們先介紹下 Invoke這個方法, 我們用SalaryEventHandler委托為例(直接調用Code 1 的類)

Code 3

1class Program
2{
3  static void Main(string[] args)
4  {
5    //添加輔助方法,查看當前的線程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],編譯器會自動的把[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(); // 讓黑屏等待,不會直接關閉..
20  }
21}

輸出的結果

圖3

從結果可以看出,他們是同一個線程調用的(都是#10).這就說明[1],[2],[3]是同步調用

[2],[3]對比[1], 只不過[2],[3]是通過委托的方式(其實我們可以說成“通過代理的方式完成”),[1]是直接的調用.舉一個我們平常生活中例子:買機票,我們到代理點購買機票而不是直接跑到機場購買,就好像我們叫別人幫我們買機票一樣,最後到手的機票是一樣的, SalaryEventHandler就是我們的代理點.所以用”代理”的方式還是直接調用的方式,他們提供的參數和返回值必須是一樣的.

接下來我們開始講異步機制核心的兩個方法BeginInvoke/EndInvoke,他們作為一個整體來完成Invoke方法的調用,不同於Inoke方法的是他們是異步執行(另外開一個線程執行)的,下面先解釋下他們的作用

BeginInvoke : 開始一個異步的請求,調用線程池中一個線程來執行

EndInvoke : 完成異步的調用, 處理返回值 和 異常錯誤.

注意: BeginInvoke和EndInvoke必須成對調用.即使不需要返回值,但EndInvoke還是必須調用,否則可能會造成內存洩漏.

我們來對比下 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)

可以看出參數的個數和類型是不同的,我們把焦點放到他們的相同點上,

1,返回值是相同的: 返回IAsyncResult 對象(異步的核心). IAsyncResult是什麼呢? 簡單的說,他存儲異步操作的狀態信息的一個接口,也可以用他來結束當前異步.具體的可以看下 http://msdn.microsoft.com/zh-cn/library/system.iasyncresult(VS.80).aspx

2,編譯器會根據委托的參數個數和類型生成相應的BeginInvoke方法,只有最後兩個參數是永遠相同的,他提供一個AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) 和一個 Object 對象.

我們再來看看EndInvoke的異同.

SalaryEventHandler 委托:

public virtual decimal EndInvoke(IAsyncResult result)

AsyncEventHandler 委托:

public virtual string EndInvoke(IAsyncResult result)

EndInvoke的參數是一樣的, 唯一是在是返回值不同(他們會根據自己委托的返回值類型生成自己的類型)

好,下面我會通過例子來說明BeginInvoke/EndInvoke,還是使用SalaryEventHandler委托為例(直接調用Code 1 的類)

.Net Framework 提供了兩種方式來使用異步方法

第一種: 通過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    //異步方法開始執行,返回IAsyncResult(存儲異常操作的狀態信息) 接口,同時EndInvoke 方法也需要他來作為參數來結束異步調用
11    asyncResult = dele.BeginInvoke(100000, 15, 100000, null, null);
12    //獲取返回值
13    decimal val = GetResult();
14    Console.WriteLine(val);
15    Console.ReadLine(); // 讓黑屏等待,不會直接關閉..
16  }
17
18  static decimal GetResult()
19  {
20    decimal val = 0;
21    //獲取原始的委托對象:先是獲取AsyncResult對象,再根據他的AsyncDelegate屬性來調用當前的(那一個)委托對象
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}

第二種: 通過回調函數. 使用倒數第二個參數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    //異步方法開始執行,使用BeginInvoke 倒數第二個參數(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(); // 讓黑屏等待,不會直接關閉..
15  }
16
17  //必須遵循AsyncCallback 委托的定義:返回值為空,一個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最後一個參數是做什麼的呢?我把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    //異步方法開始執行,看最後一個參數(Object對象) [Note1:],這裡我們傳遞2000(int)
9    dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, 2000);
10
11    Console.ReadLine(); // 讓黑屏等待,不會直接關閉..
12  }
13
14  static void GetResultCallBack(IAsyncResult asyncResult)
15  {
16    //[Note1:],他的作用就是來 "傳遞額外的參數",因為他本身是Object對象,我們可以傳遞任何對象
17    int para = (int)asyncResult.AsyncState;
18    Console.WriteLine(para);//輸出:2000
19  }
20}

異步的異常處理

接下來再講講EndInvoke,獲取最後的返回值之外,他的一個重要的應用在”引發異常來從異步操作返回異常”

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(); // 讓黑屏等待,不會直接關閉..
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發生異常,會在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"); //引發異常
36    return salary * monthCount + bonus;
37  }
38}

我們主動在YearlySalary方法中引發異常,BeginInvoke開始異步調用的時候捕獲到了這個異常,.Net Framework會在EndInvoke得到原始的異常.

說到這裡,大家是否可以簡單的應用委托來開始自己的異步操作呢? 下面看看我是怎樣為我自己的類添加異步的.

第1步, 類的定義,需要遵循.Net Framework 的規則

1)同步和異步是同時並存的

2)從最上面的兩個委托SalaryEventHandler與 AsyncEventHandler生成的BeginInvoke / EndInvoke 對比中看出,我們也來定義我們自己的異步方法,我們遵循微軟設計師異步方法設計的規則,Begin+同步方法名 / End+同步方法名

BeginXXX 必須返回IAsyncResult對象,後兩位參數必須為AsyncCallback callback, object state,前面的參數和同步方法的參數一樣

EndXXX 參數必須為IAsyncResult對象,返回值為同步方法的返回值

Code 6.1

1 public 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的規則 ,編寫我們自己的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 //編寫我們自己的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步: 調用我們編寫的類

Code 6.2

1 class 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(); // 讓黑屏等待,不會直接關閉..
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的方法的異步重構在下面的代碼中可以下載到.

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved