其它异步编程模式
委托中有两个方法:BeginInvoke和EndInvoke,可以用来实现异步执行
三种模式
等待一直到完成、轮询、回调
- 在等待—直到完成模式中,在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等异步方法完成之后再继续。
- 在轮询模式中,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其他的事情。
- 在回调模式中,原始线程一直执行,无需等待或检查发起的线程是否完成。在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用
EndInvoke
之前处理异步方法的结果。
BeginInvoke和EndInvoke
BeginInvoke
- 在调用BeginInvoke时,参数列表中的实际参数组成如下:
- 引用方法需要的参数;
- 两个额外的参数——callback参数和state参数。
- BeginInvoke从线程池中获取--个线程并且让引用方法在新的线程中开始运行。
- BeginInvoke返回给调用线程一个实现IAsyncResult接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的当前状态,原始线程然后可以继续执行。
例
如下的代码给出了一个调用委托的BeginInvoke方法的示例。第一行声明了叫做MyDel的委托类型。下一行声明了一个和委托匹配的叫做sum的方法。
之后的行声明了一个叫做del的MyDel委托类型的委托对象,并且使用sum方法来初始化它的调用列表。
最后一行代码调用了委托对象的BeginInvoke方法并且提供了两个委托参数3和5,以及两个BeginInvoke的参数callback和state,在本例中都设为null。执行后,BeginInvoke方法进行两个操作。
从线程池中获取一个线程并且在新的线程上开始运行Sum方法,将3和5作为实参。
它收集新线程的状态信息并且把IAsyncResult接口的引用返回给调用线程来提供这些信息。调用线程把它保存在一个叫做iar的变量中。
delegate long MyDel(int first, int second); //委托声明
...
static long Sum(int x, int y){...} //方法匹配委托
...
MyDel del = new MyDel(Sum); //创建委托对象
//IAsyncResult iar新线程
// del.BeginInvoke()异步调用委托
//null额外参数
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
EndInvoke
EndInvoke方法用来获取由异步方法调用返回的值,并且释放线程使用的资源
特性
- 它接受一个由BeginInvoke方法返回的IAsyncResult对象的引用,并找到它关联的线程。
- 如果线程池的线程已经退出,EndInvoke做如下的事情。
- 它清理退出线程的状态并且释放其资源。
- 它找到引用方法返回的值并且把它作为返回值。
- 如果当EndInvoke被调用时线程池的线程仍然在运行,调用线程就会停止并等待,直到清理完毕并返回值。因为EndInvoke是为开启的线程进行清理,所以必须确保对每一个BeginInvoke都调用EndInvoke。
- 如果异步方法触发了异常,在调用EndInvoke时会抛出异常。
例
如下的代码行给出了一个调用EndInvoke并从异步方法获取值的示例
//long resule异步方法的返回值
//del委托对象
//iar IAsyncResult对象
long resule = del.EndInvoke(iar);
//simeInt Out参数
long resule = del.EndInvoke(out simeInt, iar);
EndInvoke
提供了从异步方法调用的所有输出,包括ref
和out
参数。如果委托的引用方法有ref或out参数,它们必须包含在EndInvoke的参数列表中,并且在IAsyncResult对象引用之前,如下所示://simeInt Out参数 long resule = del.EndInvoke(out simeInt, iar);
等待一直到结束模式
在这种模式里,原始线程发起一个异步方法的调用,做一些其他处理,然后停止并等待,直到开启的线程结束。
例
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
//在发起线程中异步执行方法的同时
//在调用线程中处理一些其他事情
...
long resule = del.EndInvoke(out simeInt, iar);
实例
static long Sum(int x, int y)
{
Console.WriteLine("Sum");
Thread.Sleep(100);
return x + y;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
Console.WriteLine("使用BeginInvoke前");
IAsyncResult iar = del.BeginInvoke(3, 5, null, null); //开始异步调用
Console.WriteLine("使用BeginInvoke后");
long result = del.EndInvoke(iar); //等待结束并获取结果
Console.WriteLine("EndInvoke之后:{0}", result);
Console.ReadKey();
}
结果
使用BeginInvoke前
使用BeginInvoke后
Sum
EndInvoke之后:8
AsyncResult类
表现异步方法的状态
- 当我们调用委托对象的
BeginInvoke
方法时,系统创建了一个AsyncResult
类的对象。然而,它不返回类对象的引用,而是返回对象中包含的IAsyncResult
接口的引用。 AsyncResult
对象包含一个叫做AsyncDelegate
的属性,它返回一个指向被调用来开启异步方法的委托的引用。但是,这个属性是类对象的一部分而不是接口的一部分。IsCompleted
属性返回一个布尔值,表示异步方法是否完成。AsyncState
属性返回一个对象的引用,作为BeginInvoke
方法调用时的state参数。它返回object类型的引用,我们会在回调模式一节中解释这部分内容。
轮询模式
在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用`IAsyncResult`对象的`IsComplete`属性来定期检查开启的线程是否完成。如果异步方法已经完成,原始线程就调用`EndInvoke`并继续。否则,它做一些其他处理,然后过一会儿再检查。
例
在下面的示例中,“处理”仅仅是由0数到10 000 000。
delegate long MyDel(int first, int second); //声明委托类型
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine("Sum");
Thread.Sleep(100);
return x + y;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3, 5, null, null); //开始异步调用
Console.WriteLine("使用BeginInvoke后");
while(!iar.IsCompleted)
{
Console.WriteLine("还未完成");
for (long i = 0; i < 1000000; i++)
; //空语句
}
Console.WriteLine("完成");
long result = del.EndInvoke(iar); //等待结束并获取结果
Console.WriteLine("EndInvoke之后:{0}", result);
Console.ReadKey();
}
}
结果
使用BeginInvoke后
Sum
还未完成
还未完成
....
还未完成
还未完成
还未完成
完成
EndInvoke之后:8
回调模式
当异步方法调用结束之后,系统调用一个用户自定义的方法来处理结果,并且调用委托的EndInvoke方法。、
这个用户自定义的方法叫做回调方法或回调。
规则
- 第一个参数callback,是回调方法的名字。
- 第二个参数state,可以是null或要传入回调方法的一个对象的引用。我们可以通过使用IAsyncResult参数的AsyncState属性来获取这个对象,参数的类型是object。
回调方法
回调方法的签名和返回类型必须和`AsyncCallback`委托类型所描述的形式一致。
它需要方法接受一个`IAsyncResult``作为参数并且返回类型是void
例
void AsyncCallback(IAsyncResulr iar)
实例
//两种方法等价
//CallWhenDone回调函数
//new AsyncCallback(CallWhenDone)使用回调方法创建委托
IAsyncResult iar1 = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
//只需要用回调方法的名称
IAsyncResult iar2 = del.BeginInvoke(3, 5, CallWhenDone, null);
BeginInvoke
的另一个参数是发送给回调方法的对象。它可以是任何类型的对象,但是参数类型是object,所以在回调方法中,我们必须转换成正确的类型。
在回调方法内调用EndInvoke
//代码接上面
void CallWhenDone(IAsyncResult iar) {
AsyncResult ar = (AsyncResult) iar;
MyDel del = (MyDel)ar.AsyncDelegate;
long Sum = del.EndInvoke(iar);
}
- 给回调方法的参数只有一个,就是刚结束的异步方法的
IAsyncResult
接口的引用。请记住,IAsyncResult
接口对象在内部就是AsyncResult
类对象。 - 尽管
IAsyncResult
接口没有委托对象的引用,而封装它的AsyncResult
类对象却有委托对象的引用。所以,示例代码方法体的第一行就通过转换接口引用为类类型来获取类对象的引用。变量ar现在就有类对象的引用。 - 有了类对象的引用,我们现在就可以调用类对象的
AsyncDelegate
属性并且把它转化为合适的委托类型。这样就得到了委托引用,我们可以用它来调用EndInvoke。
实例
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; //一个实现 IAsyncResult 接口的抽象基类
MyDel del = (MyDel)ar.AsyncDelegate;
long result = del.EndInvoke(iar);
Console.WriteLine(" This result is:{0}", result);
}
static void Main(string[] args)
{
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.");
}
}
结果
Before BeginInvoke
Doing more work in Main
Inside Sum
Inside CallWhenDone
This result is:8
Done with Main. Exiting.
计时器
计时器提供了另外一种定期地重复运行异步方法的方式
之前代码经常使用的Thread.Sleep(500);
命名空间:using System.Threading;
签名
void TimeCallback(object state)
注意
- 计时器在每次时间到期之后调用回调方法。回调方法必须是
Timercallback
委托形式的,结构如下所示。它接受了一个object类型作为参数,并且返回类型是void。 - 当计时器到期之后,系统会从线程池中的线程上开启一个回调方法,提供state对象作为其参数,并且开始运行。
- 我们可以设置的计时器的一些特性如下。
dueTime
是回调方法首次被调用之前的时间。如果dueTime被设置为特殊的值Timeout.Infinite
,则计时器不会开始。如果被设置为0,回调函数会被立即调用。period
是两次成功调用回调函数之间的时间间隔。如果它的值设置为Timeout.Infinite,回调在首次被调用之后不会再被调用。state
可以是null或在每次回调方法执行时要传人的对象的引用。
Time类
Timer类的构造函数接受回调方法名称、dueTime、period以及state作为参数
形式
Time(TimerCallback callback, object state, uint dueTime, uint period)
例
//MyCallback回调函
//someObject传给回调的对象
//2000在2000毫秒后第一次调用
//1000每1000毫秒调用一次
Timer myTimer = new Timer(MyCallback, someObject, 2000, 1000);
一旦Timer对象被创建,我们可以使用Change方法来改变它的dueTime或period方法。
实例
Timer started
Processing timer event,1
Processing timer event,2
Processing timer event,3
.....
还有其它的一些计时器:System.Windows,Forms.Timer
、System.Timers.Timer