Q.C人生

君子博学而日参省乎己,则知明而行无过矣

导航

 

 Contents

The Heart of the APM: IAsyncResult
Implementing the APM
Testing and Performance
Conclusion

 

Slow and unpredictable are words that typically characterize I/O operations. When an application performs a synchronous I/O operation, the application is basically giving up control to the device that is doing the actual work. For example, if an application calls the StreamRead method to read some bytes from a FileStream or NetworkStream, there is no telling how much time will pass before that method returns. If the file being read is on a local hard drive, then Read may return almost immediately. If the remote server housing the file is offline, then the Read method may wait several minutes before timing out and throwing an exception. During this time, the thread making the synchronous request is tied up. If that thread is the UI thread, the application is frozen and stops responding to user input.

      低速和不可预见性时对I/O操作特征的经典描述,当一个应用程序执行一个同步I/O操作是,这个应用程序基本上放弃了对实际工作设备的控制,举例来说,如果一个应用程序调用StreamRead方法从FileStreamNetWorkStrem中读一些位,没有人能说清楚该方法返回前需要多少时间,如果从本地硬盘读取返回几乎是立即方式的.如果文件所处的远程服务器已经离线了,那么这个Read方法可能会等待几分钟然后产生一个异常.在这期间建立同步请求的线程是挂起的(tied up) ,如果这是一个UI线程,那么应用程序将会被冻结并且停止对用户的响应.

 

A thread waiting for synchronous I/O to complete is blocked, which means that thread is idle but is not allowed to perform useful work. To improve scalability, many application developers create more threads. Unfortunately, each thread introduces significant overhead such as its kernel object, user-mode and kernel-mode stacks, increased context switching, the calling of DllMain methods with thread attach/detach notifications, and so on. The result is actually reduced scalability.

一个等待同步I/O完成是一个块(blocked),这意味着.线程本身是空闲的,但不允许执行其他有用的工作,为了提高程序的等级(Scalability),很多应用程序的开发者,创建了更多的线程.不幸的是每个线程的引入都带来了巨大的开销(significant overhead) : Kernel object, user-mode and kernel-mode stacks,增加了上下文间的切换,DllMain在线程间的attach/detach通知等等,结果是降低了应用程序的等级.

An application that wishes to remain responsive to the user, improve scalability and throughput, and increase reliability should not perform I/O operations synchronously. Instead, the application should use the common language runtime (CLR) Asynchronous Programming Model (APM) to perform asynchronous I/O operations. Much has been written about how to use the CLR APM, including Chapter 23 of my book CLR via C#, 2nd Edition (Microsoft Press®, 2006). However, I'm not aware of anything that has been written explaining how to define a class with methods that implement the APM. So I decided to focus on this topic for this column.

一个应用程序希望保持用户的响应,提高应用等级和吞吐量(through port),并且增加程序的可靠性,那么就不应该使用同步方式执行I/O操作,应使用CLR的异步编程模型(Asynchronous Programming Model (APM)),更多的讲解包括在我的<CLR via C# 2 end>这本书的23章中,然而我没有意识到书中没有解释如何定义一个带有方法的类实现APM,这个专栏将关注这一主题.

There are basically four reasons why a developer would want to implement the APM itself. First, you might be building a class that communicates directly with hardware (such as a file system on a hard disk, a network, a serial port, or a parallel port). As already mentioned, device I/O is unpredictable and therefore an application should perform asynchronous I/O operations when communicating with hardware in order for the application to remain responsive, scalable, and reliable.

4个主要原因来说明为什么开发者需要自己实现APM,首先,你可能需要编写一个类直接进行硬件访问(例如:硬盘上的文件系统,网络,串口或并口),如前所述(As Already mentioned),设备的I/O操作具有不确定性,因此为了使得应用程序保持用户响应,提高等级和可信度,对于同硬件有通讯的应用程序应使用异步方法调用.

Fortunately, the Microsoft® .NET Framework already includes classes that communicate with many hardware devices. Thus, you would not need to implement the APM yourself unless you were defining a class that communicated with a hardware device not already supported by the Framework Class Library (FCL), such as a parallel port. However, there are devices that are supported by the FCL, yet certain sub-features are not supported. In this situation, you might need to implement the APM if you wanted to perform I/O operations. For example, the FCL does offer a FileStream class that lets your application communicate with a disk, but the FileStream doesn't let you access opportunistic locks , sparse file streams , or other cool features offered by the NTFS file system. If you wanted to write P/Invoke wrappers to call the Win32® APIs that exposes these features, you would want the wrappers to support the APM, thus allowing the operations to be performed asynchronously.

有幸的是,.net FrameWork已经包含了很多硬件设备类.因此你不必自己去实现APM,除非你需要通讯的硬件不被FCL支持比如:并口;然而有些被FCL支持的设备的某些子特征是不被FCL支持的.在这种情况下如果你想执行I/O操作就必须自己实现APM.举个例子,FCL提供了FileStream类让你的应用程序同硬盘进行通讯,FileStream,并没有提供可以访问机会锁,稀疏文件系统或其他的NTFS很酷的特征,如果你想些P/Invoke包装Win32 API 来使用这些特征,那么应该使该包装类支持APM实现异步操作.

Second, you might be building an abstraction layer over an already defined class that communicates directly with hardware. Several examples of this already exist in the .NET Framework. For instance, let's say you're sending data to a Web service method. On the Web service client proxy class, there is a method that accepts your arguments, which could be a set of complex data structures. Internally, the method serializes these complex data structures into an array of bytes. The byte array is then sent over the wire by using a NetworkStream class (which already has the ability to communicate with the hardware using asynchronous I/O). Another example occurs when accessing a database. The ADO.NET SqlCommand type offers BeginExecuteNonQuery, BeginExecuteReader, and other BeginXxx methods that parse arguments to send data over a network to a database. When the I/O completes, the corresponding EndExecuteNonQuery, EndExecuteReader, and other EndXxx methods are called. Internally, these EndXxx methods parse the resulted data and return rich data objects back to the caller.

第二,你可能想建立一个抽象层来控制一下已经定义的直接通硬件通讯的类.一些这样的例子存在于.Net FrameWok. 你发送数据到WebService方法中.在这个WebService客户端的Proxy类中,有一个方法能够接受你的参数,这可能是个复杂的数据结构,在内部这个方法将这个数据结构序列化到一个位数组中,并将这个位数组通过NetWorkStream送出(NetWorkStream已经具有通硬件的异步I/O操作特性).另外一个例子,发生在对数据库的访问中,ADO.NetSqlCommand提供了BeginExecuteNonQuery, BeginExecuteReader, 和其他的 BeginXxx 方法来分析参数并发送给数据库,I/O结束后相应的EndExecuteNonQuery, EndExecuteReader, 和其他 EndXxx 方法被调用,并将富含数据的结果返回给调用者.

 

Third, your class might offer a method that could potentially take a long time to execute. In this case, you might want to offer BeginXxx and EndXxx methods, allowing the callers the convenience of the APM. Unlike the previous examples, which ended up being I/O-bound operations, this time your method is performing a compute-bound operation. Because the operation is compute-bound, a thread must be used to perform the work; you are just defining BeginXxx and EndXxx methods as a convenience to users of your class.

第三,你的类可能需要一个运行很长时间的方法.在这种情况下你应该想到提高BeginxxxEndxxx方法.许可调用者从APM中获益.而不像前述例子中那样以I/O-bound结束操作,这次你的方法执行了Compute-bound操作.因为是Compute-bound操作,必须有一个线程执行这项工作,你也必须在使用户获益的类中提供,BeginxxxEndxxx的方法.

 

Finally, your class might wrap a Win32 method that performs synchronous I/O. Unfortunately, there are a number of Win32 methods that perform I/O but for which Win32 offers no way to perform these I/O operations asynchronously. For example, the Win32 registry and event logging functions could communicate with a local or remote registry/event log. Microsoft could create asynchronous versions of these Win32 functions allowing a thread not to block on them. However, as of today, asynchronous versions of these Win32 functions do not exist. When I wrap these kinds of methods with managed code, I always offer BeginXxx and EndXxx methods so that I can do the right thing in managed code even though my application is not as efficient as it could be because my methods must have a thread block while Windows® performs the synchronous I/O operation. However, if Microsoft ever adds asynchronous versions of these methods to Win32, I could change my wrappers to take advantage of the new methods, thereby gaining efficiency, without having to change my client code at all.

最后你的类可能包装了一个win32 方法执行同步I/O操作,不幸的是,有大量的Win32方法虽然执行I/O操作但无法实现异步, win32的注册表和事件日志函数能访问本地或远程的注册表或日志,Microsft可能会建立异步版本的这些Win32函数,来许可一个线程以非block的方式执行,然而那不是现在.异步版本的这些Win32函数并不存在,当我包装这种类型的方法到托管代码是,我总是提供BeginxxxEndxxx方法来保证在托管代码中的正确使用,即使应用程序没有提高效率,因为该方法仍然使用Block方法来进行I/O同步调用,将来一旦Microsoft 提供了异步版本的Win32函数,我只需修改我的包装类就可以获得高的性能,二没必要修改客户端代码.

 

The Heart of the APM: IAsyncResult

At the heart of the CLR APM is the IAsyncResult interface, which is defined as shown in Figure 1.

APM的心脏:IAsyncResult

作为CLR APM的核心,IAsyncResult 定义了如下接口