一、委托异步执行
当委托对象被调用时,它调用其调用列表中包含的方法。这是同步完成的。
如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步执行这个方法。委托类有两个方法,叫作 BeginInvoke 和 EndInvoke。
-
当调用委托的 BeginInvke 方法时,它开始在一个独立线程上执行引用方法,之后立即返回到原始线程。原始线程可以继续,而引用方法会在线程池的线程中并行执行。
-
当程序希望获取已完成的异步方法的结果时,可以检查 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性,或调用委托的 EndInvoke 方法来等待委托完成。
原始线程如何知道发起的线程已经完成:
-
在等待直到完成(wait-until-done)模式中,在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等异步方法完成之后再继续。
-
在轮询(polling)模式中,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其他的事情。
-
在回调(callback)模式中,原始线程一直执行,无须等待或检查发起的线程是否完成。
在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用 EndInvoke 之前处理异步方法的结果。
原始线程是操作系统内核在创建进程时创建的第一个线程。
二、BeginInvoke 和 EndInvoke
BeginInvoke:
-
在调用 BeginInvoke 时,参数列表中的实际参数:
- 引用方法需要的参数;
- 两个额外的参数—— callback 参数和 state 参数。
-
BeginInvoke 从线程池中获取一个线程并且让引用方法在新的线程中开始运行。
-
BeginInvoke 返回给调用线程一个实现 IAsyncResult 接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的当前状态。然后原始线程可以继续执行。
delegate long MyDel(int first,int second);//委托声明
...
static long Sun(int x,int y){ return x + y; }//方法匹配委托
...
MyDel del = new MyDel(Sum);//创建委托对象
IAsyncResult iar = del.BeignInvoke(3,5,null,null);
//IAsyncResult iar:有关新线程的信息
//del.BeignInvoke:异步调用委托
//3,5:委托参数
//callback(null) 和 state(null):额外参数
//最后一行代码的程序执行步骤:
//1、从线程池中获取一个线程并且在新的线程上运行 Sum 方法,将3和5作为实参。
//2、它收集新线程的状态信息并且把 IAsyncResult 接口的引用返回给调用线程来提供这些信息。调用线程把它保存在一个叫作 iar 的变量中。
EndInoke 方法用来获取由异步方法调用返回的值,并且释放线程使用的资源。EndInvoke 有如下的特性。
-
它接受一个由 BeginInvoke 方法返回的 IAsyncResult 对象的引用作为参数,并找到它关联的线程。
-
如果线程池的线程已经退出,则 EndInvoke 做如下的事情。
- 清理退出线程的状态并释放其资源。
- 找到引用方法返回的值并把它作为返回值返回。
-
如果当 EndInvoke 被调用时线程池的线程仍然在运行,调用线程就会停止并等待它完成,然后再清理并返回值。因为 EndInvoke 是为开启的线程进行清理,所以必须确保对每一个 BeginInvoke 都调用 EndInvoke。
-
如果异步方法触发了异常,则在调用 EndInvoke 时会抛出异常。
long result = del.EndInvoke(iar);
//long result:异步方法的返回值
//del:委托对象
//iar:IAsyncResult 对象
EndInvoke 提供了异步方法调用的所有输出,包括 ref 和 out 参数。如果委托的引用方法有 ref 或 out 参数,则它们必须包含在 EndInvoke 的参数列表中,并且在 IAsyncResult 对象引用之前:
long result = del.EndInvoke(out someInt,iar);
//long result:异步方法的返回值
//out someInt:Out 参数
//iar:IAsyncResult 对象
等待直到完成模式
IAsyncResult iar = del.BeginInvoke(3,5,null,null);
//在发起线程中异步执行方法的同时,
//在调用线程中处理一些其他事情
...
long result =del.EndInvoke(iar);
示例:
delegate long MyDel(int first, int second);//声明委托类型
class Program
{
static long Sum(int x,int y)
{
Console.WriteLine(" Inside Sum");
return x + y;
}
static void Main(string[] args)
{
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:{ result }");
Console.ReadKey();
}
}
输出结果:
Before BeginInvoke
After BeginInvoke
Doing stuff
Inside Sum
After EndInvoke:8
三、AsyncResult 类
BeginInvoke 返回一个 IASyncResult 接口的引用(该接口由一个 AsynResult 类型的类实现)。AsyncResult 类代表了异步方法的状态。
-
当我们调用委托对象的 BeginInvke 方法时,系统创建了一个 AsyncResult 类对象。然而,它不返回类对象的引用,而是返回对象中包含的 IAsyncResult 接口的引用。
-
AsyncResult 对象包含一个叫作 AsyncDelegate 的属性,它返回一个指向被调用来启动异步方法的委托的引用。但是,这个属性是类对象的一部分而不是接口的一部分。
-
IsCompleted 属性返回一个布尔值,表示异步方法是否完成。
-
AsyncState 属性返回对象的一个引用,作为 BeginInvoke 方法调用时的 state 参数。它返回 object 类型引用。
四、轮询模式
在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用 IAsyncResult 对象的 IsComplete 属性来定期检查开启的线程是否完成。如果异步方法已经完成,原始线程就调用 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 Main(string[] args)
{
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:{ result }");
Console.ReadKey();
}
}
输出结果:
After BeginInvoke
Not Done
Inside Sum
Not Done
Done
Result:8
五、回调模式
一旦初始线程发起了异步方法,它就自己管自己了,不再考虑同步。当异步方法带哦用结束之后,系统调用一个用户自定义的方法(回调方法)来处理结果,并且调用委托的 EndInvoke 方法。
BeginInvoke 参数:
- callback: 回调方法的名字(是 AsyncCallback 类型的委托)
- state: 要传入回调方法的一个对象的引用。(任何类型的对象)
1、回调方法
void AsyncCallback(IAsyncResult iar)
使用回调方法
IAsyncResult iar1 = del.BeginInvoke(3,5,new AsyncCallback(CallWhenDone),null);
IAsyncResult iar2 = del.BeginInvoke(3,5,new CallWhenDone,null);
//
2、在回调方法内调用 EndInvoke
如果不将 BeginInvoke 的 state 参数用于其他目的,可以使用它给回调方法发送委托的引用:
IAsyncResult IAR = del.BeginInvoke(3,5,CallWhenDone,del);
//del:委托对象
//第二个del:把委托对象作为状态参数发送
可以从作为参数发送给方法的 IAsyncResult 对象中提取出委托的引用。
void CallWhenDone(IAsyncResult iar)
{
AsyncResult ar = (AsyncResult) iar;//获取类对象的引用
MyDel del = (MyDel)ar.AsyncDelegate;//获取委托的引用
long Sum = del.EndInvoke(iar);//调用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;
MyDel del = (MyDel)ar.AsyncDelegate;
long result = del.EndInvoke(iar);
Console.WriteLine(" The 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("Doning more work in Main.");
Thread.Sleep(500);
Console.WriteLine("Done with Main. Exiting.");
Console.ReadKey();
}
}
输出结果:
Before BeginInvoke
Inside Sum
Doning more work in Main.
Inside CallWhenDone.
The result is : 8.
Done with Main. Exiting.
六、计时器
一种定期重复运行异步方法的方法。
- 计时器在每次到期之后调用回调方法。
void TimerCallback(object state)
- 当计时器到期之后,系统会在线程池中的一个线程上设置回调方法。
- 可以设置的计时器的特性:
- dueTime: 回调方法首次被调用之前的时间。如果 dueTime 被设置为特殊的值 Timeout.Infinite,则计时器不会开始;如果被设置为0,则回调函数被立即调用。
- period: 两次成功调用回调函数之间的时间间隔。如果它的值被设置为 Timeout.Infinite,则回调在首次被调用之后不会再被调用。
- state: 可以是 null 或在每次回调方法执行时要传入的对象的引用。
Timer(TimerCallback callback, object state, uint dueTime, uint period)
如果创建了 Timer 对象的实例:
Timer myTimer = new Timer(MyCallback, someObject, 2000, 1000);
//MyCallback:回调的名字
//someObject:传给回调的对象
//2000:在2000毫秒后第一次调用
//1000:每1000 毫秒调用一次
示例:
class Program
{
int TimmesCalled = 0;
void Display(object state)
{
Console.WriteLine($"{ (string)state } { ++TimmesCalled }");
}
static void Main(string[] args)
{
Program p = new Program();
Timer myTimer = new Timer(p.Display, "Processing timer event", 2000, 1000);
Console.WriteLine("Timer started.");
Console.ReadLine();
Console.ReadKey();
}
}
书上说大概5秒后会终止输出结果,但是我的编程环境里,并不会这样,会一直不停地输出。代码例子和书上的一模一样的。按理说,这并不会结束地呼出结果,才是对的吧?先不管了,以后遇到了这个情况再看看。