什么是.Net的异步机制 (第一部分)
2009-07-29 00:41 博弈IT 阅读(504) 评论(0) 编辑 收藏 举报什么是.Net的异步机制(委托Delegate) - step 1
在阅读下面知识前,我已经认为你已经具有c#的基础,包括简单的委托知识; 代码使用VS2008开发,但是会在.Net Framework 2.0(C Sharp)编写
什么是.Net异步机制呢?
在解释这个话题前,我们先看看同步的程序,就是我们常用的Hello World 程序.
Code 1:
2 {
3 static void Main(string[] args)
4 {
5 // 查看当前的线程ID, 是否线程池里面的线程
6 Console.WriteLine("1,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
7
8 AsyncTest test = new AsyncTest();
9 string val = test.Hello("Andy Huang");
10
11 Console.WriteLine(val);
12 Console.ReadLine(); // 让黑屏等待,不会直接关闭..
13 }
14 }
15
16 public class AsyncTest
17 {
18 public string Hello(string name)
19 {
20 // 查看当前的线程ID, 是否线程池里面的线程
21 Console.WriteLine("2,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
22 return "Hello:" + name;
23 }
24 }
图1
我们可以从图1看出,我们平常写的Hello 程序是同一个线程的,而且不是线程池理的线程程序.
按照上面的程序稍做改动, 那么开始我们第一个异步的Hello World 程序.
使用.Net 的委托机制来为我们的程序提供异步操作行为.
1步, 为我们的AsyncTest(Hello方法) 声明一个委托
public delegate string AsyncEventHandler(string name);
2步,使用委托提供的BeginInvoke, EndInvoke 方法(具体使用下一篇文章详细介绍)来提供异步的调用.
string val = test.Hello("Andy Huang");
修改为
AsyncEventHandler async = test.Hello;
IAsyncResult result = async.BeginInvoke("Andy Huang", null, null);
string val = async.EndInvoke(result);
下面是完整的代码.
Code 2:
2 {
3 static void Main(string[] args)
4 {
5 // 查看当前的线程ID, 是否线程池里面的线程
6 Console.WriteLine("1,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
7
8 AsyncTest test = new AsyncTest();
9 //把Hello 方法分配给委托对象
10 AsyncEventHandler async = test.Hello; //注释1,建议用=,不用+=
11
12 //发起一个异步调用的方法,赋值"Andy Huang", 返回IAsyncResult 对象
13 IAsyncResult result = async.BeginInvoke("Andy Huang", null, null);
14
15 //这里会阻碍线程,直到方法执行完毕
16 string val = async.EndInvoke(result);
17
18 Console.WriteLine(val);
19 Console.ReadLine(); // 让黑屏等待,不会直接关闭..
20 }
21 }
22
23 //我们使用委托来提供.Net的异步机制
24 public delegate string AsyncEventHandler(string name);
25 public class AsyncTest
26 {
27 public string Hello(string name)
28 {
29 // 查看当前的线程ID, 是否线程池里面的线程
30 Console.WriteLine("2,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
31 return "Hello:" + name;
32 }
33 }
注释1: 用=操作符在于分配委托对象时候不需要初始化;并且异步调用时候只能有一个目标方法.
图2
对比图1 和图2, ,可以看出(2,Thread ID:#10,Is PoolThread?True),在使用异步机制的时候,其实就是开启一个新的线程来执行我们的方法,并且这个线程来自 线程堆. 到这里,我们就很好的解释了”什么是.Net异步机制?”
说到这里,结束了吗? 可能大家会想其实使用异步就是多了个委托( public delegate string AsyncEventHandler(string name);) 那么到底为我们做了些什么呢?
通过反编译(微软提供的IL Disassembler)我们看到
图3
编译器为我们生成了下面类定义(其实委托就是一个类)
Code 3
public sealed class AsyncEventHandler : MulticastDelegate
{
public AsyncEventHandler(object @object, IntPtr method)
{....}
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
{...}
public virtual string EndInvoke(IAsyncResult result)
{...}
public virtual string Invoke(string name)
{...}
}
继承于MulticastDelegate, 提供了BeginInvoke / EndInvoke / Invoke.
Invoke 是我们Code 1 同步调用的方法,当然我们也可以直接使用Invoke来同步调用.
BeginInvoke / EndInvoke 是Code 2中使用的异步调用的方法,下篇文章我会详细介绍
什么时候使用.Net异步机制呢?
异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序仍然可以继续执行当前的程序。
.NET Framework 的许多方面都支持异步编程功能,这些方面包括:
· 文件(File) IO、流(Stream) IO、套接字(Socket) IO。
· 网络。
· 远程处理信道(HTTP、TCP)和代理。
· 使用 ASP.NET 创建的 XML Web services。
· ASP.NET Web 窗体。
· 使用 MessageQueue 类的消息队列。
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1, 代码
什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) - step 2
上一篇文章(什么是.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
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
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
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
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
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
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
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
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
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
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的方法的异步重构在下面的代码中可以下载到.
下一篇中我们会说说异步中的一些高级应用,异步的核心,还有微软.Net Framework 为我们提供的各种类中(具有异步方法的类),我们是怎么使用这些方法的.最后要提醒下大家:滥用异步,会影响性能,而且增加编程难度
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码 (VS2008开发,.Net Framework 2.0(C Sharp)编写)
什么是.Net的异步机制(APM核心:IAsyncResult) - step 3
在上一篇文章(什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) - step 2 ), 我们已经简单介绍了异步的调用方式, 下面我们来看看异步的核心.
异步的核心: IAsyncResult
Asynchronous Programming Model
整个异步调用过程中都是围绕IAsyncResult来进行的,大家可以看看上篇文章的例子,BeginXXX 返回这个对象,EndXXX接收这个对象来结束当前异步对象,下面我们来看看IAsyncResult 接口成员/和实现此接口的AsyncResult类成员(其中有些在上篇中已经涉及到)
IAsyncResult接口
2 {
3 WaitHandle AsyncWaitHandle { get; } //阻塞一个线程,直到一个或多个同步对象接收到信号
4 Boolean IsCompleted { get; } //判读当前异步是否完成
5 Object AsyncState { get; } //获取额外的参数值,请看上一篇文章的Code 4.3
6 Boolean CompletedSynchronously { get; } //几乎没有使用
7 }
AsyncResult类
2 {
3 //IAsyncResult 的实现
4 public virtual WaitHandle AsyncWaitHandle { get; }
5 public virtual bool IsCompleted { get; }
6 public virtual object AsyncState { get; }
7 public virtual bool CompletedSynchronously { get; }
8
9 // 其他一些重要的属性
10 public bool EndInvokeCalled { get; set; } //检验是否调用了EndInvoke()
11 public virtual object AsyncDelegate { get; } //获取原始的委托对象,可查看上一篇文章中的Code 4.1/4.2/5
12 }
注意:基本上都是只读属性
下面我们来看看异步的执行顺序,并回顾下 IAsyncResult 下各个属性的应用,如果还是不熟悉请看前2篇文章.
Code 1:
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
6
7 AsyncTest test = new AsyncTest();
8 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary;
9 //使用回调函数
10 AsyncCallback callback = new AsyncCallback(OnSalaryCallback);
11 IAsyncResult ar = del.BeginInvoke(100000, 15, 100000, callback, 2000);
12
13 DoAntherJob();
14 Console.ReadLine(); // 让黑屏等待,不会直接关闭..
15 }
16
17 //开始其他工作.
18 static void DoAntherJob()
19 {
20 Thread.Sleep(1000);//需要1秒才能完成这个工作,注1
21 Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
22 }
23
24 static void OnSalaryCallback(IAsyncResult asyncResult)
25 {
26 //通过AsyncState 获取额外的参数.
27 decimal para = (int)asyncResult.AsyncState;
28
29 //通过AsyncDelegate 获取原始的委托对象
30 AsyncResult obj = (AsyncResult)asyncResult;
31 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32
33 if (asyncResult.IsCompleted)// 判读是否已经调用完成
34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
35
36 decimal val = del.EndInvoke(asyncResult);
37
38 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val + para, Thread.CurrentThread.ManagedThreadId);
39 }
40 }
41
42 public class AsyncTest
43 {
44 public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
45 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
46 {
47 //模拟耗时/复杂的逻辑计算.
48 Thread.Sleep(3000);//等待3秒,注2
49 return salary * monthCount + bonus;
50 }
51 }
图1
我们看到DoAntherJob 比异步YearlySalary快2秒,看代码中(注1)和(注2),两个线程的执行结果
接下来,我们说说AsyncWaitHandle 属性. 他返回WaitHandle对象(System.Threading.WaitHandle), 他有3个重要的方法. WaitOne / WaitAny / WaitAll ,我们先来说下WaitOne,在Code1代码基础上只是增加了下面红色部分.
1,WaitOne
Code 1.1
IAsyncResult ar = del.BeginInvoke(100000, 15, 100000, callback, 2000);
//阻碍当前线程,直到异步调用结束.
ar.AsyncWaitHandle.WaitOne();
//开始其他工作.
DoAntherJob();
图1.1
执行输出,对比图1我们可以看到执行的次序不一样了(看时间),调用WaitOne,会阻碍当前线程,直到异步完成,才释放当前的线程, WaitOne 提供了时间的重载版本WaitOne(int millisecondsTimeout)/ WaitOne(TimeSpan timeout);来判断阻碍的时间.无参的版本是无限等待的(直到异步调用结束)
2, WaitAll
我们在Code1的代码基础上加上Hello的异步调用(使Main提供多个异步调用),注意红色部分.
Code 1.2
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
6
7 AsyncTest test = new AsyncTest();
8 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary;
9 MyThirdAsyncCode.AsyncTest.AsyncEventHandler asy = test.Hello;
10
11 IAsyncResult salayAsyc = del.BeginInvoke(100000, 15, 100000, OnSalaryCallback, null);
12 IAsyncResult helloAsyc = asy.BeginInvoke("Hello Andy", OnHelloCallback, null);
13 //把所有异步的句柄保存到WaitHandle 对象中
14 WaitHandle[] handles = { salayAsyc.AsyncWaitHandle, helloAsyc.AsyncWaitHandle };
15 //阻碍当前线程,直到所有异步调用结束.
16 WaitHandle.WaitAll(handles);
17
18 //开始其他工作.
19 DoAntherJob();
20 Console.ReadLine(); // 让黑屏等待,不会直接关闭..
21 }
22 static void DoAntherJob()
23 {
24 Thread.Sleep(1000);//需要1秒才能完成这个工作,注1
25 Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
26 }
27 static void OnSalaryCallback(IAsyncResult asyncResult)
28 {
29 //通过AsyncDelegate 获取原始的委托对象
30 AsyncResult obj = (AsyncResult)asyncResult;
31 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32
33 if (asyncResult.IsCompleted)// 判读是否已经调用完成
34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
35
36 decimal val = del.EndInvoke(asyncResult);
37 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId);
38 }
39
40 static void OnHelloCallback(IAsyncResult asyncResult)
41 {
42 //通过AsyncDelegate 获取原始的委托对象
43 AsyncResult obj = (AsyncResult)asyncResult;
44 MyThirdAsyncCode.AsyncTest.AsyncEventHandler del =
(MyThirdAsyncCode.AsyncTest.AsyncEventHandler)obj.AsyncDelegate;
45
46 if (asyncResult.IsCompleted)// 判读是否已经调用完成
47 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
48
49 string val = del.EndInvoke(asyncResult);
50 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId);
51 }
52 }
53
54 public class AsyncTest
55 {
56 public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
57 public delegate string AsyncEventHandler(string name); // 对应Hello 方法
58 public string Hello(string name)
59 {
60 //模拟耗时/复杂的逻辑计算.等待5秒
61 Thread.Sleep(5000);
62 return "Hello:" + name;
63 }
64 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
65 {
66 //模拟耗时/复杂的逻辑计算.
67 Thread.Sleep(3000);//等待3秒
68 return salary * monthCount + bonus;
69 }
70 }
图1.2
从图1.2中可以看出,WaitAll会阻碍当前线程(主线程#10),等待所有异步的对象都执行完毕(耗时最长的异步),才释放当前的线程,WaitAll/WaitAny的重载版本和WaitOne一样.
3, WaitAny
和WaitAll 基本上是一样的.我们可以使用 WaitAny 来指定某个/某几个委托先等待,修改Code1.2红色部分,使用WaitAny.
Code1.3
//把salayAsyc异步的句柄保存到WaitHandle 对象中
WaitHandle[] handles = { salayAsyc.AsyncWaitHandle };
//阻碍当前线程,直到所有异步调用结束.
WaitHandle.WaitAny(handles);
图1.3
我们阻碍了DoAntherJob(#10)线程,直到Salary异步调用计算完成.同样我们可以巧用这三个方法来改变我们方法执行的顺序.
释放资源
Code2
2 {
3 //通过AsyncDelegate 获取原始的委托对象
4 AsyncResult obj = (AsyncResult)asyncResult;
5 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
6
7 decimal val = del.EndInvoke(asyncResult);
8 asyncResult.AsyncWaitHandle.Close();//显示的释放资源
9 }
当开始调用BeginXXX后,就会创建一个新的AsyncResult对象.这个对象会构造一个WaitHandle句柄(通过AsyncWaitHandle访问),当我们EndXXX后,并不会马上关闭这个句柄,而是等待垃圾收集器来关闭,这时候我们最后在调用EndXXX完成后,显示的关闭这个句柄.
说到这里,我们基本上把异步方法都解释一遍,下面我们来看看重构的异步对象,我们也可以细细体会异步对象的内部执行代码..下面Code3.1/3.2/3.3代码来自Jeffery Richard大师的Power Threading类库,具体可查看http://msdn.microsoft.com/en-us/magazine/cc163467.aspx
重构的异步对象
1步,构造一个内部无参的AsyncResultNoResult对象,继承IAsyncResult接口(保留原创的注释)
Code3.1
2 {
3 // Fields set at construction which never change while
4 // operation is pending
5 private readonly AsyncCallback m_AsyncCallback;
6 private readonly Object m_AsyncState;
7
8 // Fields set at construction which do change after
9 // operation completes
10 private const Int32 c_StatePending = 0;
11 private const Int32 c_StateCompletedSynchronously = 1;
12 private const Int32 c_StateCompletedAsynchronously = 2;
13 private Int32 m_CompletedState = c_StatePending;
14
15 // Field that may or may not get set depending on usage
16 private ManualResetEvent m_AsyncWaitHandle;
17
18 // Fields set when operation completes
19 private Exception m_exception;
20
21 public AsyncResultNoResult(AsyncCallback asyncCallback, Object state)
22 {
23 m_AsyncCallback = asyncCallback;
24 m_AsyncState = state;
25 }
26
27 public void SetAsCompleted(
28 Exception exception, Boolean completedSynchronously)
29 {
30 // Passing null for exception means no error occurred.
31 // This is the common case
32 m_exception = exception;
33
34 // The m_CompletedState field MUST be set prior calling the callback
35 Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
36 completedSynchronously ? c_StateCompletedSynchronously :
37 c_StateCompletedAsynchronously);
38 if (prevState != c_StatePending)
39 throw new InvalidOperationException(
40 "You can set a result only once");
41
42 // If the event exists, set it
43 if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
44
45 // If a callback method was set, call it
46 if (m_AsyncCallback != null) m_AsyncCallback(this);
47 }
48
49 public void EndInvoke()
50 {
51 // This method assumes that only 1 thread calls EndInvoke
52 // for this object
53 if (!IsCompleted)
54 {
55 // If the operation isn't done, wait for it
56 AsyncWaitHandle.WaitOne();
57 AsyncWaitHandle.Close();
58 m_AsyncWaitHandle = null; // Allow early GC
59 }
60
61 // Operation is done: if an exception occured, throw it
62 if (m_exception != null) throw m_exception;
63 }
64
65 Implementation of IAsyncResult
115 }
2步,继承AsyncResultNoResult对象,并且为他提供返回值和泛型的访问
Code3.2
2 {
3 // Field set when operation completes
4 private TResult m_result = default(TResult);
5
6 public AsyncResult(AsyncCallback asyncCallback, Object state) :
7 base(asyncCallback, state) { }
8
9 public void SetAsCompleted(TResult result,
10 Boolean completedSynchronously)
11 {
12 // Save the asynchronous operation's result
13 m_result = result;
14
15 // Tell the base class that the operation completed
16 // sucessfully (no exception)
17 base.SetAsCompleted(null, completedSynchronously);
18 }
19
20 new public TResult EndInvoke()
21 {
22 base.EndInvoke(); // Wait until operation has completed
23 return m_result; // Return the result (if above didn't throw)
24 }
25 }
3步,模拟长时间的异步工作
Code3.3
2 {
3 private Int32 m_ms; // Milliseconds;
4
5 public LongTask(Int32 seconds)
6 {
7 m_ms = seconds * 1000;
8 }
9
10 // Synchronous version of time-consuming method
11 public DateTime DoTask()
12 {
13 Thread.Sleep(m_ms); // Simulate time-consuming task
14 return DateTime.Now; // Indicate when task completed
15 }
16
17 // Asynchronous version of time-consuming method (Begin part)
18 public IAsyncResult BeginDoTask(AsyncCallback callback, Object state)
19 {
20 // Create IAsyncResult object identifying the
21 // asynchronous operation
22 AsyncResult<DateTime> ar = new AsyncResult<DateTime>(
23 callback, state);
24
25 // Use a thread pool thread to perform the operation
26 ThreadPool.QueueUserWorkItem(DoTaskHelper, ar);
27
28 return ar; // Return the IAsyncResult to the caller
29 }
30
31 // Asynchronous version of time-consuming method (End part)
32 public DateTime EndDoTask(IAsyncResult asyncResult)
33 {
34 // We know that the IAsyncResult is really an
35 // AsyncResult<DateTime> object
36 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;
37
38 // Wait for operation to complete, then return result or
39 // throw exception
40 return ar.EndInvoke();
41 }
42
43 // Asynchronous version of time-consuming method (private part
44 // to set completion result/exception)
45 private void DoTaskHelper(Object asyncResult)
46 {
47 // We know that it's really an AsyncResult<DateTime> object
48 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;
49 try
50 {
51 // Perform the operation; if sucessful set the result
52 DateTime dt = DoTask();
53 ar.SetAsCompleted(dt, false);
54 }
55 catch (Exception e)
56 {
57 // If operation fails, set the exception
58 ar.SetAsCompleted(e, false);
59 }
60 }
61 }
来自Jeffrey Richter大师更多更详细的异步操作方法, 请查看http://www.wintellect.com /PowerThreading.aspx,对于一些朋友可能看不懂Code3.1-3.3代码(其实没什么所谓的),因为涉及到过多的线程知识,这里出 于让你获得更多的更深层次的(异步)认识为目的,才提供上面代码,在以后的文章会再次探讨.
下一篇章中,我们来看看微软提供有异步调用的类是如何调用的,并从中我会给出些真实应用环境中的一些小技巧,让你编写的代码更健壮更完善.
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码(VS2008开发,.Net Framework 2.0(C Sharp)编写)