使用APM执行受计算限制的异步操作

  我们可以通过APM来调用任何方法,但首先,我们需要定义一个与我们希望调用的方法拥有相同签名的方法。以计算1至n间总和的方法为例(这是一个计算密集的任务,它不执行任何I/O操作)。

private static UInt64 Sum(UInt64 n)
{
UInt64 sum = 0;
for(UInt64 i = 1; i <= n; i++)
{
checked
{
//在此使用了checked代码,以便于当结果sum与类型UInt64不符时抛出一个异常
sum += i;
}
}
return sum;
}

  如果n非常大,Sum方法将需要执行很长时间。因此,我们希望异步执行该方法。为做到这一点,首先定义一个与希望异步调用的方法(Sum)拥有相同签名的委托。

internal delegate UInt64 SumDelegate(UInt64 n);

  C#编译器将上一行代码编译为一个类定义,它的逻辑形式如下:

internal sealed class SumDelegate : MulticastDelegate
{
public SumDelegate(Object object, IntPtr method);
public UInt64 Invoke(UInt64 n);
public IAsyncResult BeginInvoke(UInt64 n, AsyncCallback callback, Object object);
public UInt64 EndInvoke(IAsyncResult result);
}

  在C#源代码中定义一个委托时,编译器通常会生成一个包含BeginInvoke和EndInvoke方法的类。BeginInvoke方法与委托定义拥有相同的参数,另外还加上两个额外的参数AsyncCallback和Object。所有的BeginInvoke方法都返回一个IAsyncResult对象。EndInvoke方法只有一个参数,即IAsyncResult,EndInvoke方法返回的就是委托签名返回的数据类型。下面的代码示范了如何使用方法回调聚集技巧来异步调用Sum方法:

public static void Main()
{
//初始化一个委托变量来引用我们希望异步调用的方法
SumDelegate sumDelegate = Sum;

//调用使用线程池线程的方法
sumDelegate.BeginInvoke(100000000, SumIsDone, sumDelegate);

//在这里执行一些其他代码

//挂起主线程,便于演示
Console.ReadLine();
}

private static void SumIsDone(IAsyncResult ar)
{
//从IAsyncResult对象中提取SumDelegate对象
SumDelegate sumDelegate = (SumDelegate)ar.AsyncState;

//获得结果
UInt64 sum = sumDelegate.EndInvoke(ar);

//显示结果
Console.WriteLine("Sum={0}", sum);
}

  BeginInvoke方法将操作加入线程池的队列,线程池中的线程将被唤醒,将工作项从队列中取出,然后调用受计算限制的操作(本例中为Sum)。通常情况下,线程池中的线程从执行方法的过程中返回时,线程就返回到线程池。但是,BeginInvoke方法调用时以一个方法的名称(SumIsDone)作为它的倒数第二个参数。因此,当Sum方法返回时,线程池中的线程并不会返回到线程池中,相反,它将调用SumIsDone方法。也就是说,受计算限制的操作完成时将调用回调方法。

APM与异常

  无论何时调用BeginXxx方法,它都可能抛出一个异常。Windows将告诉应用程序异步操作已错误完成,为实现这一点,Windows会向CLR线程池发送一个通知。最后我们的代码通过调用EndXxx方法将操作结果聚集。通常情况下,EndXxx方法将操作结果返回到代码中,但如果操作失败,EndXxx方法将抛出一个异常。因此,我们在调用EndXxx方法时应该考虑处理异常情况。如下所示:

private static void SumIsDone(IAsyncResult ar)
{
//从IAsyncResult对象中提取SumDelegate对象
SumDelegate sumDelegate = (SumDelegate)ar.AsyncState;

try
{
UInt64 sum = sumDelegate.EndInvoke(ar);
Console.WriteLine("Sum = {0}", sum);
}
catch(OverflowException)
{
Console.WriteLine("Sum can't be shown: number is too big.");
}
}

 

posted on 2011-11-05 19:37  辛勤的代码工  阅读(674)  评论(0编辑  收藏  举报