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