同步I/O

在Windows系统中,应用程序调用I/O函数发起的操作均发生在用户态,而用户态是无法访问I/O硬件设备的,因此Windows把用户态的I/O操作信息打包发送给内核,此时进入内核态访问对应的I/O设备。I/O设备处理请求的操作时,发起I/O请求的线程将被阻塞,只有当I/O操作完成时,才会重新唤醒线程以返回结果。如果在Socket中使用同步I/O操作,当大量客户端请求连接时,将消耗大量线程,造成性能的急剧下降。

异步I/O

异步I/O与同步I/O不同之处在于,当内核向I/O设备发送请求后,线程将直接返回继续执行其它任务。当设备处理完I/O请求后,Windows将处理结果放入任务队列等待线程处理,对于CLR,则是放入线程池任务队列,等待线程池线程处理。CLR异步I/O实际上是对Windows I/O完成端口的封装。

异步方法

.NET4.5提供了全新的async和await关键字来取代以前的Beginxxx和Endxxx系列异步方法。一个简单的例子:

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
static void Main()
{
1: Console.WriteLine("@Main=====Before async,线程ID:"+ Thread.CurrentThread.ManagedThreadId);
2: Task t = DoWorkAsync();
3: Console.WriteLine("@Main=====After async,线程ID:" + Thread.CurrentThread.ManagedThreadId);
4: while (!t.IsCompleted)
{
Console.WriteLine("@Main=====Waiting completed...,线程ID:" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(200);
}
Console.Read();
}

static async Task DoWorkAsync()
{
5: Console.WriteLine("@DoWorkAsync=====Before await,线程ID:" + Thread.CurrentThread.ManagedThreadId);
6: await Task.Run(() => { Console.WriteLine("Doing long-time work,线程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); });
7: Console.WriteLine("@DoWorkAsync=====After await,线程ID:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("@DoWorkAsync=====Job Done,线程ID:" + Thread.CurrentThread.ManagedThreadId);
}

/*输出
@Main=====Before async,线程ID:1
@DoWorkAsync=====Before await,线程ID:1
Doing long-time work,线程ID:3
@Main=====After async,线程ID:1
@Main=====Waiting completed...,线程ID:1
@Main=====Waiting completed...,线程ID:1
@Main=====Waiting completed...,线程ID:1
@DoWorkAsync=====After await,线程ID:3
@DoWorkAsync=====Job Done,线程ID:3
*/

很显然async修饰的方法并不是字面意义上的异步方法,而是被内部的await关键字分裂成为两部分。主线程调用DoWorkAsync时会以同步方式执行到await,然后将await修饰的任务交给线程池处理,并立即返回一个与之关联的Task对象。现在主线程继续执行后面的语句,与此同时耗时任务由线程池线程异步执行。等待耗时任务完成之后,线程池线程继续执行await之后的语句,此时DoWorkAsync才真正执行完毕。用序号表示的执行流程图为:

异步流程

对于GUI程序而言,情况又有所不同。异步方法总是会使用GUI线程的SynchronizationContext执行await后续部分(CUI程序的同步上下文为null),因此上述的After await部分将由主线程执行,如果不慎在主线程中以同步方式等待结果,将造成死锁。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyForm:Form
{
MyForm()
{
string s=GetHttpAsync().Result;
//...
}
}

private async GetHttpAsync()
{
HttpResponseMessage msg=await new HttpClient().GetAsync("http://www.baidu.com");
//===After await===
return await msg.Content.ReadAsStringAsync();
}

GUI线程以同步方式等待Result,此时线程被阻塞。异步执行完HttpClient().GetAsync()后需要主线程接着执行After await部分以返回结果,然而主线程被阻塞,因此造成死锁。注意例子在CUI环境下正常运行。为了防止死锁,.NET提供了Task.ConfigureAwait(bool)方法,传递false时与CUI环境行为一致。

 评论




载入天数...载入时分秒...  |  总访问量为
Powered by Github and MarkdownPad 2

--------------------------- 别拉了,我是有底线的>_< ---------------------------