上一篇文章(什么是.Net的异步机制(委托Delegate) - step 1)中,我已经解释了什么是异步编程,那么现在我们就开始具体的说怎样异步编程.
我们怎样进行异步编程/开发?
现在扩充下上篇文章的类(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的异步机制
2
public
delegate
string
AsyncEventHandler(
string
name);
//
对应Hello 方法
3
public
delegate
decimal
SalaryEventHandler(
decimal
salary,
int
monthCount,
decimal
bonus);
//
对应YearlySalary方法
4
public
class
AsyncTest
5
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
6
public string Hello(string name)
7![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
8
return "Hello:" + name;
9
}
10![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
11![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
/**//// <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![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
20
//添加辅助方法,查看当前的线程ID
21
Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
22![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
23
return salary * monthCount + bonus;
24
}
25
}
这里用.NET Reflector 5 来反编译,之所以用这个,因为比微软的会更加清晰明了.如果想了解这个工具的朋友可查看(http://reflector.red-gate.com/)
![](https://i-blog.csdnimg.cn/blog_migrate/5d0c611af5692192fdf0cc1e82de42b3.jpeg)
图1
开始我先对图1中的小图标进行个简单的解释
= 类(Class)
= 类继承的基类
= sealed(委托)
= 类的构造函数
= 方法
= virtual方法
下面我们先比较下SalaryEventHandler与 AsyncEventHandler委托的异同.
1) SalaryEventHandler
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);
![](https://i-blog.csdnimg.cn/blog_migrate/f555b830ff4d49304d5529402f6c9395.jpeg)
图2.1
编译器生成的类Code2.1(图2.1)
Code 2.1
1
public
sealed
class
SalaryEventHandler : MulticastDelegate
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
public SalaryEventHandler(object @object, IntPtr method)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
.}
5
public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus,
AsyncCallback callback, object @object)
6![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
7
public virtual decimal EndInvoke(IAsyncResult result)
8![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
9
public virtual decimal Invoke(decimal salary, int monthCount, decimal bonus)
10![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
11
}
2) AsyncEventHandler
public delegate string AsyncEventHandler(string name);
![](https://i-blog.csdnimg.cn/blog_migrate/12271471eb910c755e2a4af9ebcedf24.jpeg)
图2.2
编译器生成的类Code2.2(图2.2)
Code2.2
1
public
sealed
class
AsyncEventHandler : MulticastDelegate
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
public AsyncEventHandler(object @object, IntPtr method)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
.}
5
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
6![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
7
public virtual string EndInvoke(IAsyncResult result)
8![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
9
public virtual string Invoke(string name)
10![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
}
11
}
对比两个委托(事实上是一个sealed 的类),都继承于System.MuliticaseDelegate, 三个virtual的 Invoke / BeginInvoke / EndInvoke 方法.
//同步方法
Invoke : 参数的个数,类型, 返回值都不相同
//异步方法,作为一组来说明
BeginInvoke : 参数的个数和类型不同,返回值相同
EndInvoke : 参数相同,返回值不同
这里我们先介绍下 Invoke这个方法, 我们用SalaryEventHandler委托为例(直接调用Code 1 的类)
Code 3
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static void Main(string[] args)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
5
//添加辅助方法,查看当前的线程ID
6
Console.WriteLine("Main Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
7![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
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![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
18
Console.WriteLine("V1:{0},V2:{1},V3:{2}", v1, v2, v3);
19
Console.ReadLine(); // 让黑屏等待,不会直接关闭..
20
}
21
}
输出的结果
![](https://i-blog.csdnimg.cn/blog_migrate/e7912cda08c3c6d6a3bfc527f39d679f.jpeg)
图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
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static IAsyncResult asyncResult;
4![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
5
static void Main(string[] args)
6![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
7![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
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![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
18
static decimal GetResult()
19![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
20
decimal val = 0;
21
//获取原始的委托对象:先是获取AsyncResult对象,再根据他的AsyncDelegate属性来调用
当前
的(那一个)委托对象
22
AsyncResult result = (AsyncResult)asyncResult;
23
SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
25
//调用EndInvoke获取返回值
26
val = salDel.EndInvoke(asyncResult);
27![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
28
return val;
29
}
30
}
第二种: 通过回调函数. 使用倒数第二个参数AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) ,建议使用这种方法.
Code 4.2
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static void Main(string[] args)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
5
AsyncTest test = new AsyncTest();
6
SalaryEventHandler dele = test.YearlySalary;
7![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
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![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
14
Console.ReadLine(); // 让黑屏等待,不会直接关闭..
15
}
16![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
17
//必须遵循AsyncCallback 委托的定义:返回值为空,一个IAsyncResult对象参数
18
static void GetResultCallBack(IAsyncResult asyncResult)
19![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
20
decimal val = 0;
21
//获取原始的委托对象
22
AsyncResult result = (AsyncResult)asyncResult;
23
SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
25
//调用EndInvoke获取返回值
26
val = salDel.EndInvoke(asyncResult);
27![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
28
Console.WriteLine(val);
29
}
30
}
BeginInvoke最后一个参数是做什么的呢?我把Code 4.2 方法修改下.
Code 4.3
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static void Main(string[] args)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
5
AsyncTest test = new AsyncTest();
6
SalaryEventHandler dele = test.YearlySalary;
7![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
8
//异步方法开始执行,看最后一个参数(Object对象) [Note1:],这里我们传递2000(int)
9
dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, 2000);
10![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
11
Console.ReadLine(); // 让黑屏等待,不会直接关闭..
12
}
13![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
14
static void GetResultCallBack(IAsyncResult asyncResult)
15![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
16
//[Note1:],他的作用就是来 "传递额外的参数",因为他本身是Object对象,我们可以传递任何对象
17
int para = (int)asyncResult.AsyncState;
18
Console.WriteLine(para);//输出:2000
19
}
20
}
异步的异常处理
接下来再讲讲EndInvoke,获取最后的返回值之外,他的一个重要的应用在”引发异常来从异步操作返回异常”
Code 5
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static void Main(string[] args)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
5
AsyncTest test = new AsyncTest();
6
SalaryEventHandler dele = test.YearlySalary;
7![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
8
dele.BeginInvoke(100000, 15, 100000, GetResultCallBack, null);
9
Console.ReadLine(); // 让黑屏等待,不会直接关闭..
10
}
11![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
12
static void GetResultCallBack(IAsyncResult asyncResult)
13![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
14
decimal val = 0;
15
//获取原始的委托对象
16
AsyncResult result = (AsyncResult)asyncResult;
17
SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
18
try
19![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
20
//如果EndInvoke发生异常,会在EndInvoke得到原始的异常.
21
val = salDel.EndInvoke(asyncResult);
22
Console.WriteLine(val);
23
}
24
catch (Exception ex)
25![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
26
Console.WriteLine(ex.Message);
27
}
28
}
29
}
30
public
delegate
decimal
SalaryEventHandler(
decimal
salary,
int
monthCount,
decimal
bonus);
//
对应YearlySalary方法
31
public
class
AsyncTest
32
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
33
public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
34![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
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
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
private delegate string AsyncEventHandler(string name);
4
private AsyncEventHandler _Async;
5
public string Hello(string name)
6![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
7
return "Hello:" + name;
8
}
9![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
10
//按照.Net Framework的规则 ,编写我们自己的BeginInvoke方法
11
public virtual IAsyncResult BeginHello(string name, AsyncCallback callback, object state)
12![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
13
AsyncEventHandler del = Hello;
14![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
15
this._Async = del;
16![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
17
return del.BeginInvoke(name, callback, state);
18
}
19
//编写我们自己的EndInvoke方法
20
public virtual string EndHello(IAsyncResult asyncResult)
21![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
22
if (asyncResult == null)
23
throw new ArgumentNullException("asyncResult");
24
if (this._Async == null)
25
throw new ArgumentException("_Async");
26![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
27
string val = string.Empty;
28
try
29![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
30
val = this._Async.EndInvoke(asyncResult);
31
}
32
finally
33![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
34
this._Async = null;
35
}
36
return val;
37
}
38
}
第2步: 调用我们编写的类
Code 6.2
1
class
Program
2
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
3
static void Main(string[] args)
4![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
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![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
12
Console.ReadLine(); // 让黑屏等待,不会直接关闭..
13
}
14![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
15
static void OnHelloCallback(IAsyncResult asyncResult)
16![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
17
//获取额外的参数
18
AsyncTest obj = (AsyncTest)asyncResult.AsyncState;
19
string val = obj.EndHello(asyncResult);
20
Console.WriteLine(val);
21
}
22
}
附:Hello的方法的异步重构在下面的代码中可以下载到.
下一篇中我们会说说异步中的一些高级应用,异步的核心,还有微软.Net Framework 为我们提供的各种类中(具有异步方法的类),我们是怎么使用这些方法的.最后要提醒下大家:滥用异步,会影响性能,而且增加编程难度![](https://i-blog.csdnimg.cn/blog_migrate/ddca338e368edcf29b7cfd58a7bf8a7d.gif)
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码 (VS2008开发,.Net Framework 2.0(C Sharp)编写)