AsyncEnumerator对EAP的支持
去年在异步编程中开始使用Wintellect's .NET Power Threading Library中的AsyncEnumerator,这个库通过将程序员较陌生的异步编程模型转化为程序员较熟悉的同步编程模型来实现异步操作,较大的改善了异步代码的易写、易读、易维护性。
目前对AsyncEnumerator介绍的文章已经比较多了,但集中于APM(Asynchronous Programming Model)模式中的应用,而.NET中,特别是Silverlight中还有很多对象是使用EAP(Event-based Asynchronous Pattern),对应的EAP与AsyncEnumerator结合的文章与例子很少,加上Jeffrey Richter不喜欢EAP模型,让我一度以为AsyncEnumerator不支持,于是自己去实现了EAP和AsyncEnumerator的结合。后来项目后期对代码重构时,有时间就再仔细看了看Power Threading的实现,发现其本身对EAP的支持是有封装的。下面是基于AsyncEnumerator使用Silverlight进行Socket通信的例子(Silverlight的Socket只有EAP模型):
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.SocketClientAccessPolicyProtocol = SocketClientAccessPolicyProtocol.Tcp;
DnsEndPoint host = new DnsEndPoint(HtmlPage.Document.DocumentUri.Host, Configurations.PORT);
socketEventArg.RemoteEndPoint = host;
AsyncEnumerator asyncEnum = new AsyncEnumerator();
EventApmFactory<SocketAsyncEventArgs> eventApmFactory
= new EventApmFactory<SocketAsyncEventArgs>();
//异步代码组织在GetEnumerator()中
//IEnumerator<int> GetEnumerator(SocketAsyncEventArgs e,AsyncEnumerator ae){...}
var enumerator = GetEnumerator(socketEventArg,asyncEnum);
EventHandler<SocketAsyncEventArgs> eventHandler =
eventApmFactory.PrepareOperation(asyncEnum.End()).EventHandler;
socketEventArg.Completed += eventHandler;
asyncEnum.BeginExecute(enumerator, asyncEnum.EndExecute);
socketEventArg.SocketClientAccessPolicyProtocol = SocketClientAccessPolicyProtocol.Tcp;
DnsEndPoint host = new DnsEndPoint(HtmlPage.Document.DocumentUri.Host, Configurations.PORT);
socketEventArg.RemoteEndPoint = host;
AsyncEnumerator asyncEnum = new AsyncEnumerator();
EventApmFactory<SocketAsyncEventArgs> eventApmFactory
= new EventApmFactory<SocketAsyncEventArgs>();
//异步代码组织在GetEnumerator()中
//IEnumerator<int> GetEnumerator(SocketAsyncEventArgs e,AsyncEnumerator ae){...}
var enumerator = GetEnumerator(socketEventArg,asyncEnum);
EventHandler<SocketAsyncEventArgs> eventHandler =
eventApmFactory.PrepareOperation(asyncEnum.End()).EventHandler;
socketEventArg.Completed += eventHandler;
asyncEnum.BeginExecute(enumerator, asyncEnum.EndExecute);
其中关键的代码是:
// 用EventApmFactory来创建一个Handler,这个Handler在每次事件触发时
// 调用asyncEnum.End(),将执行控制交还Enumerator,
// Enumerator接着上次yield return之后的地方执行
EventHandler<SocketAsyncEventArgs> eventHandler =
eventApmFactory.PrepareOperation(asyncEnum.End()).EventHandler;
socketEventArg.Completed += eventHandler;
// 调用asyncEnum.End(),将执行控制交还Enumerator,
// Enumerator接着上次yield return之后的地方执行
EventHandler<SocketAsyncEventArgs> eventHandler =
eventApmFactory.PrepareOperation(asyncEnum.End()).EventHandler;
socketEventArg.Completed += eventHandler;
这个代码比我自己的EAP封装代码要简化很多,此外,Jeffrey在BLog中对Task与AsyncEnumerator的结合做了说明:
private static IEnumerator<Int32> AsyncEnumeratorAndTasks(AsyncEnumerator ae) {
var t = new Task<DateTime>(() => { Thread.Sleep(10000); return DateTime.Now; });
t.Start();
// The Task tells the AsyncEnumerator when it is done
// If you don’t need to identify the Task, you can pass ‘null’ instead of ‘task’
t.ContinueWith(task => ae.End()(task));
yield return 1; // Waits for the 1 Task to complete
// You MUST call DequeueAsyncResult to Remove the entry form the AsyncEnumerator object’s collection
// Casting the return value and assigning to ‘t’ is not necessary; since ‘t’ already refer to the same Task object
t = (Task<DateTime>) ae.DequeueAsyncResult();
Console.WriteLine(t.Result); // Shows the DateTime when the Task completed
}
var t = new Task<DateTime>(() => { Thread.Sleep(10000); return DateTime.Now; });
t.Start();
// The Task tells the AsyncEnumerator when it is done
// If you don’t need to identify the Task, you can pass ‘null’ instead of ‘task’
t.ContinueWith(task => ae.End()(task));
yield return 1; // Waits for the 1 Task to complete
// You MUST call DequeueAsyncResult to Remove the entry form the AsyncEnumerator object’s collection
// Casting the return value and assigning to ‘t’ is not necessary; since ‘t’ already refer to the same Task object
t = (Task<DateTime>) ae.DequeueAsyncResult();
Console.WriteLine(t.Result); // Shows the DateTime when the Task completed
}