APM的介绍请参见《CLR Via C# 第3版》笔记之(二十一) - 异步编程模型(APM)。
EAP是Event-based Asynchronous Pattern的缩写,指基于事件的异步模式。
主要内容:
- EAP和APM的比较
- APM转换为Task
- EAP转换为Task
1. EAP和APM的比较
EAP是基于事件的异步模型,比如winform中很多事件***Changing,***Changed,***Completed等等。
这里的异步是指这些事件方法(即***Changing方法,***Completed方法等)是被异步调用的。
但是这些事件方法执行时是同步的,比如***Changing方法执行时,UI是无响应的。
所以我们遇到处理时间较长的操作时,应该让这些事件方法尽快返回,在事件方法中将那些耗时的操作通过其他异步方式调用(比如多线程,Task等)。
Jeffrey大牛喜爱APM的方式的异步对EAP方式不太感冒。
我本身觉得APM和EAP各有自己的优点,可能是应用场景不同。以下是我自己的一些理解:
|
应用场景 |
资源消耗 |
回调方法 |
EAP |
适合事件驱动的程序(比如GUI程序)的开发,利用EAP配合visual studio可以很方便的实现各种功能 |
内部封装较多,占用资源较多 |
可以在事件方法中用【+=】或【-=】注册和注销多个回调方法。 事件触发时,所有注册的回调的方法都会被执行,动态调用回调方法较困难 |
APM |
适用于服务端程序,利用Begin***和End***,可以应对大量的请求 |
占用资源少 |
可以动态的调用指定的回调方法 |
下面用个简单的例子来展现EAP和APM是如何实现异步的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
using
System;
using
System.Windows.Forms;
using
System.Net;
using
System.Threading;
class
CLRviaCSharp_22
{
static
void
Main(
string
[] args)
{
MyFormEAP eap =
new
MyFormEAP();
eap.ShowDialog();
MyFormAPM apm =
new
MyFormAPM();
apm.ShowDialog();
}
}
internal
class
MyFormEAP : Form
{
public
MyFormEAP()
{
Text =
"(EAP)Click in the window to start a web request"
;
Width = 800;
Height = 600;
}
protected
override
void
OnClick(EventArgs e)
{
// 开始异步web请求
Text =
"(EAP)web request initilized"
;
var
client =
new
WebClient();
client.DownloadStringCompleted += ProcessString;
base
.OnClick(e);
}
private
void
ProcessString(
object
sender, DownloadStringCompletedEventArgs e)
{
Text =
"(EAP)Content length: "
+ e.Result.Length;
}
}
internal
class
MyFormAPM : Form
{
public
MyFormAPM()
{
Text =
"(APM)Click in the window to start a web request"
;
Width = 800;
Height = 600;
}
protected
override
void
OnClick(EventArgs e)
{
// 开始异步web请求
Text =
"(APM)web request initilized"
;
webreq.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webreq);
base
.OnClick(e);
}
private
void
ProcessWebResponse(IAsyncResult result)
{
var
req = (WebRequest)result.AsyncState;
using
(
var
res = req.EndGetResponse(result))
{
Text =
"Content length: "
+ res.ContentLength;
}
}
private
static
AsyncCallback SyncContextCallback(AsyncCallback callback)
{
SynchronizationContext sc = SynchronizationContext.Current;
// 如果没有同步上下文,直接返回传入的东西
if
(sc ==
null
)
return
callback;
// 返回一个委托,这个委托将委托Post到捕捉到的SC中
return
asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
}
}
|
从上面的例子也可以看出,在基于事件的操作上,EAP方式的代码要比APM方式简单的多。
实现方式上,
APM方式用Begin***和End***方式实现异步
EAP用一个异步请求(DownloadStringAsync)和完成事件时回调函数的注册(client.DownloadStringCompleted += ProcessString;)来实现异步
利用Task的ContinuWith方法,可以APM和EAP转换为一个Task。
下面两节演示如何将上例中的APM方式和EAP方式转换为一个Task。
2. APM转换为Task
主要利用TaskFactory中的FromAsync方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
using
System;
using
System.Windows.Forms;
using
System.Net;
using
System.Threading;
using
System.Threading.Tasks;
class
CLRviaCSharp_22
{
static
void
Main(
string
[] args)
{
MyFormAPM apm =
new
MyFormAPM();
apm.ShowDialog();
}
}
internal
class
MyFormAPM : Form
{
public
MyFormAPM()
{
Text =
"(APM)Click in the window to start a web request"
;
Width = 800;
Height = 600;
}
protected
override
void
OnClick(EventArgs e)
{
// 开始异步web请求
Text =
"(APM)web request initilized"
;
Task.Factory.FromAsync<WebResponse>(webreq.BeginGetResponse, webreq.EndGetResponse,
null
)
.ContinueWith(task =>
{
var
sc = SynchronizationContext.Current;
sc.Post(state => { Text =
"(APM)Content length: "
+ task.Result.ContentLength; },
null
);
}, TaskContinuationOptions.ExecuteSynchronously);
base
.OnClick(e);
}
}
|
这里有个很重要的地方,就是Task的ContinueWith方法里设置的TaskContinuationOptions.ExecuteSynchronously参数。
刚开始我没有设置这个参数,怎么也得不到SynchronizationContext.Current(每次都是null)。所以设置不了Text属性。
3. EAP转换为Task
主要利用TaskCompletionSource这个类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
using
System;
using
System.Windows.Forms;
using
System.Net;
using
System.Threading;
using
System.Threading.Tasks;
class
CLRviaCSharp_22
{
static
void
Main(
string
[] args)
{
MyFormEAP eap =
new
MyFormEAP();
eap.ShowDialog();
}
}
internal
class
MyFormEAP : Form
{
public
MyFormEAP()
{
Text =
"(EAP)Click in the window to start a web request"
;
Width = 800;
Height = 600;
}
protected
override
void
OnClick(EventArgs e)
{
// 开始异步web请求
Text =
"(EAP)web request initilized"
;
var
client =
new
WebClient();
var
tcs =
new
TaskCompletionSource<
string
>();
// DownloadStringCompleted实在GUI线程上执行的
client.DownloadStringCompleted += (sender, ea) =>
{
tcs.SetResult(ea.Result);
};
// TaskContinuationOptions.ExecuteSynchronously参数保证了
// ContinueWith也在GUI线程上
tcs.Task.ContinueWith(task =>
{
Text =
"(EAP)Content length: "
+ task.Result.Length;
}, TaskContinuationOptions.ExecuteSynchronously);
base
.OnClick(e);
}
}
|