异步的核心: IAsyncResult
Asynchronous Programming Model
整个异步调用过程中都是围绕IAsyncResult来进行的,大家可以看看上篇文章的例子,BeginXXX 返回这个对象,EndXXX接收这个对象来结束当前异步对象,下面我们来看看IAsyncResult 接口成员/和实现此接口的AsyncResult类成员(其中有些在上篇中已经涉及到)
IAsyncResult接口
1
public
interface
IAsyncResult
2
{
3 WaitHandle AsyncWaitHandle { get; } //阻塞一个线程,直到一个或多个同步对象接收到信号
4 Boolean IsCompleted { get; } //判读当前异步是否完成
5 Object AsyncState { get; } //获取额外的参数值,请看上一篇文章的Code 4.3
6 Boolean CompletedSynchronously { get; } //几乎没有使用
7 }
AsyncResult类
1
public
class
AsyncResult : IAsyncResult, IMessageSink
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:
1
class
Program
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
1
class
Program
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
1
static
void
OnSalaryCallback(IAsyncResult asyncResult)
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
1
internal
class
AsyncResultNoResult : IAsyncResult
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#region Implementation of IAsyncResult
66 public Object AsyncState { get { return m_AsyncState; } }
67
68 public Boolean CompletedSynchronously
69 {
70 get
71 {
72 return Thread.VolatileRead(ref m_CompletedState) ==
73 c_StateCompletedSynchronously;
74 }
75 }
76
77 public WaitHandle AsyncWaitHandle
78 {
79 get
80 {
81 if (m_AsyncWaitHandle == null)
82 {
83 Boolean done = IsCompleted;
84 ManualResetEvent mre = new ManualResetEvent(done);
85 if (Interlocked.CompareExchange(ref m_AsyncWaitHandle,
86 mre, null) != null)
87 {
88 // Another thread created this object's event; dispose
89 // the event we just created
90 mre.Close();
91 }
92 else
93 {
94 if (!done && IsCompleted)
95 {
96 // If the operation wasn't done when we created
97 // the event but now it is done, set the event
98 m_AsyncWaitHandle.Set();
99 }
100 }
101 }
102 return m_AsyncWaitHandle;
103 }
104 }
105
106 public Boolean IsCompleted
107 {
108 get
109 {
110 return Thread.VolatileRead(ref m_CompletedState) !=
111 c_StatePending;
112 }
113 }
114 #endregion
115 }
2步,继承AsyncResultNoResult对象,并且为他提供返回值和泛型的访问
Code3.2
1
internal
class
AsyncResult
<
TResult
>
: AsyncResultNoResult
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
1
internal
sealed
class
LongTask
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代码(其实没什么所谓的),因为涉及到过多的线程知识,这里出于让你获得更多的更深层次的(异步)认识为目的,才提供上面代码,在以后的文章会再次探讨.
下一篇章中,我们来看看微软提供有异步调用的类是如何调用的,并从中我会给出些真实应用环境中的一些小技巧,让你编写的代码更健壮更完善.