CLR Via C# 3rd 阅读摘要 -- Chapter 27 – I/O-Bound Asynchronous Operations

How Windows Performs I/O Operations

  1. Windows如何处理同步I/O操作?
    figure27-1
  2. IRP:I/O Request Packet;
  3. 当硬件设备执行I/O操作时,发出I/O请求的线程无事可做,Windows会让该线程休眠所以并不会浪费CPU;
  4. Windows如何处理异步I/O操作?
    figure27-2
  5. .NET异步文件I/O,注意要有FileOptions.Asynchronous,然后调用BeginRead();
  6. 完成的IRP从线程池中使用FIFO算法抽取;
  7. 当线程完成它的工作并返回到线程池中,线程池不会让它马上处理新的工作项直到CPUs再次饱和之后,因此减少了上下文切换而改进了性能。如果线程池稍后确定拥有的线程比实际需要的多,它会让这些多余的线程自行终结,回收这些线程占有的资源;
  8. 在CLR内部,线程池使用Windows资源I/O Completion Ports来实现异步I/O行为;
  9. SilverLight版本的FCL没有提供同步I/O操作,因为在浏览器进程中运行的缘故。

The CLR's Asynchronous Programming Model(APM)

  1. FCL中支持的APM的类型:
    • 所有和硬件设备通讯的继承自System.IO.Stream的提供BeginWrite()和BeginRead()方法的类型;注意那些继承自System.IO.Stream但是不跟硬件设备通讯的类型,比如MemoryStream等,也提供了适合APM的BeginWrite()和BeginRead()方法,但是这些方法执行的是计算相关的操作,而不是I/O相关的操作,因此需要请求一个线程来执行计算相关的操作,而I/O相关的操作要少一个线程;
    • System.Net.Dns提供了BeginGetHostAddress(), BeginGetHostByName(), BeginGetHostEntry(), BeginResolve();
    • System.Net.Sockets.Socket类提供BeginAccept(), BeginConnect(), BeginDisconnect(), BeginReceive(), BeginReceiveFrom(), BeginReceiveMessageFrom(), BeginSend(), BeginSendFile(), BeginSendTo();
    • 所有继承自System.Net.WebRequest的类型,提供了BeginGetRequestStream(), BeginGetResponse()方法;
    • Sytem.IO.Ports.SerialPort有一个只读属性BaseStream,有BeginRead()和BeginWrite()方法;
    • System.Data.SqlClient.SqlCommand类提供了BeginExecuteQuery(), BeginExecuteReader(), BeginExecuteXmlReader()。
  2. 每个BeginXxx()方法都有两个额外的参数:userCallback(:AsyncCallback委托类型)、stateObject(任何希望发送到userCallback的对象引用,在回调函数中,通过参数(IAsyncResult接口)的只读属性AsyncState来获得);
  3. 当调用BeginXxx()方法时,首先构造出一个唯一的对象标识出I/O请求,然后将该I/O请求加入到Windows设备驱动的队列中,接着返回一个IAsyncResult对象的引用。可以把这个对象看成是你异步调用的凭据。

The AsyncEnumerator Class

  1. APM的麻烦之处:
    • 必须分隔代码到多个回调方法中;
    • 必须避免使用参数和本地变量,因为这些变量没有分配在栈空间上,所以不能被其他线程和方法访问;
    • 有些C#的代码结构中,比如try/catch/finally, using, for, do, while, foreach不能被使用;
    • 有些特性比较难以实现,比如协调多个并发操作,支持取消和超时,通知GUI线程刷新控件等。
  2. AsyncEnumerator可以解决这些APM的麻烦,提供的特性有:
    • 和现有的.NET技术简便集成;
    • 协调多个异步并发操作;
    • 支持分组取消;
    • 通过调用每个操作的EndXxx()方法自动取消一组异步操作,如果不关心结果的话;
    • 从烦心应用程序的线程模型中解放出来;
    • 异常处理支持;
    • 调试支持。

The APM and Exceptions

  1. HTTP(RFC 2616)申明客户端应用最多同时两个并发线程连接服务器。但是现在很多浏览器和服务器已经没有这个限制了;
  2. 如何在设计服务器程序时解除两个并发线程同时连接的限制:调整System.Net.ServicePointManager.DefaultConnectionLimit属性。

Applications and Their Threading Models

  1. GUI程序只允许创建窗体的线程更新界面控件;
  2. 当线程池线程开始处理客户端请求,可以假设客户端的文化特性,允许Web服务器返回文化特定的数字、日期、时间等;
  3. 通过查询GUI线程的.SynchronizationContext.Current属性,可以获得一个SynchronizationContext的引用,可以将该引用保存在共享变量中。这样无论什么时候需要GUI线程修改UI时,就可以让线程池线程引用保存的对象并调用Post方法,将该引用通过将要被GUI线程激发的方法。
  4. 推荐使用Post(内部BeginInvoke())方法,它将回调压入到GUI线程的队列并允许线程池线程立即返回。而Send内部Invoke()方法,不会立即返回,会被阻塞;
  5. ASP.NET Web窗体和XML Web服务程序,处理客户端请求开始运行的线程,会有一个继承自SynchronzationContext的对象跟它相关联。

Implementing a Server Asynchronously

  1. 如何实现异步的ASP.NET Web窗体页面,在.aspx文件的头部加入Page指示"Async=true"。然后在代码中调用AddOnPreRenderCompleteAsync方法;
  2. 如何实现异步的ASP.NET Web服务,提供两个方法BeginXxx(),EndXxx(),并加上属性标签[WebMethod]
  3. 如何实现异步的WCF Web服务,定义符合模式的BeginXxx(), EndXxx()方法,然后用[OperationContract(AsyncPattern=true)]属性标签标记BeginXxx()。

The APM and Compute-Bound Operations

  1. I/O请求会压入到Windows设备驱动的队列中。然而,委托的BeingInvoke方法通过内部调用TheadPool.QueueUserWorkItem()将计算方向的操作压入到CLR的线程池队列。

APM Considerations

  1. APM要注意的问题:
    • 不通过线程池使用APM;
        APM提供三种途径来知晓异步操作是否完成:
      • 线程在操作完成之前传入IAsyncResult对象调用EndXxx();
      • 一个线程可以被阻塞等待其他操作完成,通过调用WaitOne()(IAsyncResult.AsyncWaitHandle);
      • 线程可以连续的查询IAsyncResult.IsCompleted属性。
    • 总是调用EndXxx()方法,并只调用一次。否则会有资源泄露;
        要求调用EndXxx()有两个原因:
      • 当你初始化一个异步操作时CLR会分配一些内部资源。当操作完成后,CLR会持有这些资源知道EndXxx()被调用;
      • 当初始化一个异步操作,你实际上并不知道该操作最终是成功还是失败。发现问题的唯一途径是调用EndXxx()查询返回值或者检查异常。
    • 调用EndXxx()方法时总是使用同样的对象;
      当调用BeginInvoke时,IAsyncResult对象内部会持有一个引用到最初对象。
    • 在调用BeginXxx()和EndXxx()方法时使用ref, out和params参数;
    • 不能取消一个异步的I/O方向的操作;
    • 内存消耗:如果你知道所要执行的I/O操作能够很快完成,那么以同步方式执行更合适;
    • 有些I/O操作必须以同步方式完成。比如Win32的CreateFile(调用FileStream的构造器)总是以同步方式执行;
      Windows Vista,微软引入了新的Win32函数CancelSynchronousIO()。该函数允许线程取消一个被其他线程执行的同步I/O操作。
      Windows没有提供异步访问注册表、访问系统事件日志、获得目录/子目录、修改文件/目录的属性、……的函数。
    • FileStream特定的问题。
      FileOptions.Asynchronous标志(等效于Win32的CreateFileFILE_FLAG_OVERLAPPED标志):如果没有指定该标志,Windows以同步方式执行所有的文件操作。当然,仍然可以调用FileStream.BeginRead()方法,看上去操作以异步方式执行,但在内部,FileStream使用另外的线程来模拟异步操作。
      当使用FileStream,必须确定是以同步还是异步方式执行,也就是FileOptions.Asynchronous标志。如果指定该标志,总是调用BeginRead(),如果不指定,总是调用Read()

I/O Request Priorities

  1. 低优先级的线程获得时间后,可以在短时间内排队大量的I/O请求,所以呢低优先级的线程可能会挂起高优先级的线程,从而严重影响系统的响应性;
  2. 关于I/O优先级,可以从这里下载I/O Prioritization in Windows Vista白皮书
  3. 相关的Win32 API:GetCurrentProcess、GetCurrentThread、CancelSynchronousIo;
  4. 进程只能影响他自己的后台处理模式;Windows不允许线程修改其他进程的后台处理模式;
  5. 优先级反转:低优先级的线程在正常优先级的线程等待时抢得了线程同步锁,正常优先级的线程可能最终等待后台优先级的线程直到低优先级I/O请求完成。

Converting the IAsyncResult APM to a Task

  1. I/O-Bound 从APM转换到Task:
    APM 代码片段:
    WebRequest webRequest = WebRequest.Create("http://wintellect.com");
         webRequest.BeginGetResponse(result => {
         WebResponse webResponse = null;
         try {
         webResponse = webRequest.EndGetRespone(result);
         Console.WriteLine("Content Length: " + webResponse.ContentLength);
         }
         catch (WebException we) {
         Console.WriteLine("Failed: " + we.GetBaseException().Message);
         }
         finally {
         if (webResponse != null) webResponse.Close();
         }
         }, null);
    Task 代码片段:
    WebRequest webRequest = WebRequest.Create("http://wintellect.com");
         Task.Factory.FromAsync<WebResponse>(
         webRequest.BeginGetResponse, webRequest.EndGetRespone, null, TaskCreationOptions.None)
         .ContinueWith(task => {
         WebResponse webResponse = null;
         try {
         webResponse = task.Result;
         Console.WriteLine("Content Length: " + webResponse.ContentLength);
         }
         catch (AggregateException ae) {
         if (ae.GetBaseException() is WebException)
         Console.WriteLine("Failed: " + ae.GetBaseException().Message);
         else
         throw;
         }
         finally {
         if (webResponse != null) webResponse.Close();
         }
         });

The Event-Based Asynchronous Pattern

  1. EAP:Event-based Asynchronous Pattern;
  2. BackgroundWorker是设计用来处理计算方向的异步工作的,别错误的用来完成I/O方向的异步工作。I/O方向的比计算方向的异步操作可以少一个线程;
  3. BackgroundWorker提供三个(EAP)事件:DoWork、ProgressChanged、RunWorkerCompleted
  4. 将EAP转换成Task:System.Threading.Tasks.TaskCompletionSource
  5. 比较APM和EAP:
    • EAP比APM最大的优点就是Visual Studiao设计时支持。GUI程序,事件处理方法由GUI线程激发;
    • EAP内部以APM方式实现,需要更多的内存并且执行速度相对较慢。实际上,EAP必须为所有的过程报告和完成事件分配多个继承自EventArg的对象;
    • 以后,静态方法和单例类不再能提供EAP,因为:程序的不同部分可以立刻注册所有的事件,当操作完成时,程序的不同部分无论是否异步操作都需要激发事件处理方法;
  6. EAP一般用于窗体程序,而且由于有额外开销,虽然在设计器上可以方便使用,但是还是不建议使用。

Programming Model Soup

  1. 比较:QueueUserWorkItem、Timer、RegisterWaitForSingleObject、Tasks、IAsyncResult APM、Event-based PM、AsyncEnumerator;
    table27-1
  2. Tasks比QueueUserWorkItem或委托的BeginInvoke性能更好;
  3. 对于ThreadPool.QueueUserWorkItem或委托的BeginInvoke,使用PreFairness标志可以获得相同的线程池行为;
  4. 可以定制TaskScheduler,可以改变调度算法而不需要改变代码或编程模型;
  5. Task对象比QueueUserWorkItem或委托的BeginInvoke会消费更多的内存;
  6. IAsyncResult APM提供了四种技术,模型比较复杂,但是如果少使用回调方法技术,模型就简单多了;
  7. 支持EAP的类多半都支持取消我行为;
  8. IAsyncResult APM支持不支持任何取消操作;
  9. EAP是基于事件的,因此可以简单的用于Windows窗体、WPF、SilverLight窗体设计器。

本章小结

    本章详细讲述了I/O方向的异步工作如何完成,首先解释了Windows处理同步I/O和异步I/O的内部机制。然后介绍了APM,针对APM的问题还介绍了一个Wintellect的辅助类AsyncEnumerator。接着讲了APM如何处理异常,以及应用程序如何选择线程模型。演示了如何实现一个异步的服务器程序(ASP.NET WEB服务、ASP.NET窗体页面、WCF)。解释了APM还可以用于计算方向的异步操作。接着,重点解释了APM的缺点以及处理办法。然后讲述了I/O请求的优先级,以及如何讲了IAsyncResult APM转换成Task。接着还介绍了一种不太优雅的异步编程模型EAP。最后针对几种异步编程模型进行了比较。

posted @ 2010-06-22 15:28  bengxia  阅读(608)  评论(0编辑  收藏  举报
无觅相关文章插件,快速提升流量