委托对象的调用列表中只有一个方法(我们通常称之为引用方法)时,可以进行异步执行。委托类有两个方法:BeginInvoke和EndInvoke就是用来实现这一效果的。
- 当我们调用委托的BeginInvoke方法时,将会在一个独立的线程执行委托的引用方法,并立即返回到原始线程(即调用方法的位置),继续向下执行。此时委托的引用方法在线程池中并行执行。
- 当程序希望获取已完成的异步方法结果时,可以检查BeginInvoke返回的IAsycnResult的IsCompleted属性,或者调用委托的EndInvoke方法来等待委托的完成。
通常我们在实际使用过程中,这种异步执行的方法常常采用三种模式:
- 等待完成模式(wait-until-done):发起异步方法后,就去完成其他处理内容,然后原始线程就中断并等待异步方法完成后才继续其他执行。
- 轮询模式(polling):原始线程通过循环定期检查异步方法是否完成,如果没有则继续做其他事情。
- 回调模式(callback):原始线程发起异步方法后就不在理会和检查结果。异步方法在完成后,自行调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果。
1. BeginInvoke和EndInvoke方法
首先我们需要了解BeginInvoke的一些重要事项:
- BeginInvoke 参数的组成:
- 委托的引用方法自身的参数;
- 两个额外参数:callback 和state参数。
- BeginInvoke从线程池中获得一个线程并让引用方法在此线程中开始运行。
- BeginInvoke会返回一个实现IAsyncResult接口的对象,这个对象可以反映线程中异步方法的实时状态。
例如:
delegate long MyDel(int first, int second);
...
static long Sum (int x,int y){...}
...
MyDel del=new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3,5,null,null);
EndInvoke 用来获取异步方法调用返回的值,并且清除释放线程中使用的资源。特征如下:
- 它接受BeginInvoke返回的IAsyncResult对象,并由此找到关联的线程;
- 如果线程池中的线程已经退出,则做如下两件事:
- 清理线程状态并释放其资源;
- 如果引用方法有返回值,则将其返回。
- 如果线程仍在运行,EndInvoke将停止并等待,直到方法完成,然后清理线程并获取返回值。因为EndInvoke负责线程清理工作,因此每一个BeginInvoke都必须有对应的EndInvoke相匹配。
- 如果异步方法触发了异常,将在调用EndInvoke方法时抛出。
如下代码示例了一个EndInvoke的调用,它必须以IAsyncResult对象的引用为参数:
long result = del.EndInvoke(iar);
EndInvoke提供了从异步方法返回的所有输出,包括ref和out参数。如果委托的引用方法有ref和out参数,它们就必须包含在EndInvoke的参数列表中。例如:
long result = del.EndInvoke(out int someInt, iar);
2. 等待完成模式
在这种模式下,原始线程发起一个异步方法的调用,做一些事情处理,然后停止并等待,直到开启线程结束。
using System;
using System.Threading;
delegate long MyDel (int first, int second);
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine(“ Inside Sum”);
Thread.Sleep(100);
return x+y;
}
static void Main()
{
MyDel del=new MyDel(Sum);
Console.WriteLine(“Before BeginInvoke”);
IAsyncResult iar=del.BeginInvoke(3, 5, null, null);
Console.WriteLine(“ After BeginInvoke”);
Console.WriteLine(“Doing stuff”);
long result = del.EndInvoke(iar);
Console.WriteLine(“After EndInvoke:{0}”, result);
}
}
3.AsyncResult类
AsyncResult 是方法的必要部分,BeginInvoke 返回一个IAsyncResult接口的引用(内部是AsyncResult类的对象)。AsyncResult类表现了异步方法的状态。
- 当我们调用委托对象的BeginInvoke方法时,系统创建一个AsyncResult类的对象,然后它不返回类对象的引用,而是返回对象中包含的IAsyncResult接口的引用。
- AsyncResult对象包含一个叫做AsyncDelegate的属性,它返回一个指向被调用来开启异步方法的委托的引用。注意,这个属性是类对象的一部分而不是接口的一部分。
- IsCompleted 属性返回异步方法是否完成的布尔值。
- AsyncState返回一个对象引用,做为BeginInvoke 方法调用时的state参数。它返回object类型的引用,只有在回调模式中使用。
4. 轮询模式
在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用IAsyncResult对象的IsCompleted属性定期检查开启的线程是否完成。如果完成则继续向下执行,否则继续做它的其他处理,直到下一次检查。
delegate long MyDel(int x, int y);
class program
{
long Sum(int x, int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
static void Main()
{
MyDel del = new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
Console.WriteLine("After BeginInvoke");
while (!iar.IsCompleted)
{
Console.WriteLine("Not Done");
for (long i = 0; i < 10000000; i++) ;
}
Console.WriteLine("Done");
long result = del.EndInvoke(iar);
Console.WriteLine("Result:{0}", result);
}
}
4. 回调模式
在之前的等待结束模式和轮询模式中,初始线程都是继续自己的控制流程,直到它知道开启的线程已经完成,然后获取结果并继续。
回调模式则与之不同,一旦初始线程发起了异步方法,由它自己管理,就不再进行管理。异步方法调用结束后,系统自动调用一个用户自定义的方法来自行处理结果,并且调用委托的EndInvoke方法。这个用户自定义的方法做做回调方法或简称回调。
BeginInvoke的参数列表中最后两个额外参数就是供回调方法使用的。
- 第一个参数callback,是回调方法的名字;
- 第二个参数state,可以是null或要传入回调方法的一个对象引用。我们可以通过使用IAsyncResult参数的AsyncState属性来获取这个对象,参数类型是object。
1)回调方法
回调方法的签名和返回类型必须与AsyncCallback委托类型所描述的形式一致。它需要方法接受一个IAsyncResult作为参数并且返回类型是void,如下所示
void AsyncCallback(IAsysnResult iar)
我们有多种方式可以为BeginInvoke方法提供回调方法。由于BeginInvoke中的callback参数是AsyncCallback类型的委托,我们可以以委托形式提供,或者,我们也可以只提供回调方法的名称,让编译器为我们创建委托,两种方法是完全等价的。
IAsyncResult iar1=del.BeginInvoke(3,5,new AsyncCallback(CallWhenDone),null)
IAsyncResult iar2=del.BeginInvoke(3,5,CallWhenDone,null)
BeginInvoke的另外一个参数是发送给回调方法的对象。它可以是任何类型的对象,但是参数类型是object。所以在回调方法中,我们必须转换成正确的类型。
2)在回调方法中调用EndInvoke
在回调方法内,我们的代码应该调用委托的EndInvoke方法来处理异步方法执行后的输出值。要调用委托的EndInvoke方法,我们肯定需要委托对象的引用,而它在初始线程中,不在开启的线程中。如果不使用state参数作为其他用途,可以使用它发送委托的引用给回调方法。
IAsyncResult iar = del,BeginInvoke(3,5,CallWhenDone,del);
- 给回调方法的参数只有一个,就是刚结束的异步方法的IAsyncResult接口的引用。请记住:IAsyncResult接口对象在内部就是AsyncResult类对象。
- 尽管IAsyncResult接口没有委托对象的引用,而封装它的AsyncResult类对象却有委托对象的引用。所以可以通过转换接口引用为类类型来获取类对象的引用。
- 有了类对象的引用,我们现在就可以调用类对象的AsyncDelegate属性并且把它转换为合适的委托类型。这样就得到了委托引用,我们可以用它来调用EndInvoke方法。
using System.Runtime.Remoting.Messaging; //包含AsyncResult类
void CallWhenDone(IAsyncResult iar)
{
AsyncResult ar = (AsyncResult)iar; // 获取类对象的引用
MyDel del = (MyDel)ar.AsyncDelegate; // 获取委托的引用
long sum = del.EndInvoke(iar); // 调用EndInvoke方法
...
}
如此我们就完成了一个使用回调模式的完整示例。为了方便阅读,我们把所有知识点放在一起:
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging; //包含AsyncResult类
namespace YbydjyLibrary
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
static void CallWhenDone(IAsyncResult iar)
{
Console.WriteLine(" Inside CallWhenDone");
AsyncResult ar = (AsyncResult)iar; // 获取类对象的引用
MyDel del = (MyDel)ar.AsyncDelegate; // 获取委托的引用
long sum = del.EndInvoke(iar); // 调用EndInvoke方法
Console.WriteLine($"Result:{sum}");
}
static void Main()
{
MyDel del = new MyDel(Sum);
Console.WriteLine("Before BeginInvoke");
IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
Console.WriteLine("Doing more work in Main");
Thread.Sleep(500);
Console.WriteLine("Done with Main. Exiting.");
}
}
}
网友评论