APM是一种编程方式,而不是C#编程语言的一种新特性(虽然实现APM而提供BeginXXX、EndXXX方法,但这个并没有改变XXX方法的本质,因此APM只是一种编程方式)
APM异常
正式因为APM只是一种编程方式,因此其异常处理方式与正常的方式一致。但有一点需要说明,一般情况下,会将try语句块放置于调用EndXXX的方法内部。
APM与计算限制
在《CLR via C#》中多线程和异步以及后面同步锁的这些部分中都包含了“计算限制”这个名称,说一下个人理解。计算限制操作是指利用计算机的性能去完成非I/O类型操作的操作统称。
APM编程模式一般是用于I/O相关的操作中,但是其仍然可以适用于“计算限制操作”中。其原理如下:定义好委托之后,编译器会将委托编译,并生成相关的方法,其中就有一个BeginInvoke和EndInvoke方法,而有了这两个方法就可以在编程时采用APM编程方式。以.NET自带的Func
public delegate TResult Func<in T, out TResult>(T arg)
定义完委托后,编译器会将委托编译为如下:
public sealed class Func<T,TResult>:MulticastDelegate{
public Func(Object object,IntPtr method);
public TResult Invoke(T arg);
public IAsyncResult BeginInvoke(T arg,AsyncCallBack callback,Object object);
public TResult EndInvoke(IAsyncResult result);
}
就是通过第三个、第四个方法完成了计算限制操作的APM编程。其中callback回调方法的委托的具体定义为:
[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void AsyncCallback(
IAsyncResult ar
)
计算限制操作使用APM编程的例子如下:
假设需完成某求和的方法,方法如下:
private static UInt64 Sum(UInt64 n){
UInt64 sum=0;
for(UInt64 i=0;i<n;i++){
checked{
sum+=i;
}
}
return sum;
}
这个方法正好符合Func
public static void Main(){
Func<UInt64,UInt64> sumDelegate=Sum;
sumDelegate.BeginInvoke(1000000,SumIsDone,sumDelegate); //最后一个参数很有意思
Console.ReadLine();
}
private static void SumIsDone(IAsyncResult result){
var sumDelegate = (Func<UInt64,UInt64>)result.AsyncState;
try{
UInt64 theSumResult=sumDelegate.EndInvoke(result);
Console.WriteLine("最终求和结果为:"+theSumResult);
}catch(Exception ex){
throw ex;
}
}
APM注意事项
1、默认情况下,CLR在异步操作完成后通过线程池调用预先定义的回调方法。若不使用系统默认,而是通过手动查询异步操作是否完成,进而再去调用回调方法,这也是可行的。我在《CLR via C#》读书笔记-异步编程(三) 中说明了如何使用IAsyncResult对象属性来判断异步是否完成。但是不建议这样做。这样会导致线程阻塞,线程池创建新的线程。
2、APM编程中一定要调用EndXXX方法
3、在调用EndXXX方法时,一定要使用与调用BeginXXX相同的对象。在上面的例子的有一行注释“最后一个参数很有意思”,这个参数的含义就是将其封装在IAsyncResult的AsyncState属性中,这样就可以在调用EndXXX的方法中得到,在调用EndXXX时能够保证,其与调用BeginXXX的对象是相同的。
4、APM编程方式无取消机制(从Vista开始,Win32中新增了一个CancelSynchronousIO函数,可以允许一个线程取消另外一个线程执行同步的操作)
5、APM会导致内存消耗。因为在异步操作创建时就会创建IAsyncResult对象。不过这种对内存的占用还是小
6、有些操作必须是同步的,因此在这些过程中是不能使用APM编程方式的。例如通过FileStream创建文件只能是一个同步操作。特别明显的感受就是在网络映射磁盘或者是U盘中创建文件,在创建文件的时候就会出现卡死的的现象。
7、FileStream特有的问题。FileStream很有意思,其在初始化的时候需要通过File.ASynchronous标志位进行标示该对象上所有的操作时同步还是异步。若使用默认时(即,采用同步的方式),虽然也可以调用BeginRead,但是其内部是通过创建一个新的线程模拟异步操作的。而这个动作是“纯属浪费,而且会影响到性能”。因此,在使用FileStream时,要一开始就要想明白是采用异步还是同步。若采用异步方式,就要调用BeginXXX,若采用同步,就采用XXX方法。这样才能保证可以获得最佳的性能。若在使用的过程中,既需要同步使用,又需要异步使用,那就设定FileStream为异步吧!或者再笨的方法就是创建两个FileStream,一个同步操作,一个异步操作。