BeginInvoke,EndInvoke,Invoke基础知识
先定义一个委托如下:
public delegate void myEventHandler(int i,out string o);
再用MSIL反汇编程序(Ildasm.exe)来观看反汇编代码
其实CLR为我们做了4件事情
1.定义一个构造器
2.定义一个虚方法BeginInvoke
3.定义一个虚方法EndInvoke
4.定义一个虚方法Invoke
(1)在反汇编代码中我看到如下片断:
.class auto ansi sealed nested public myEventHandler
extends [mscorlib]System.MulticastDelegate
{
} // end of class myEventHandler
由此可以知道其实我们声明的委托myEventHandler就是一个密封类
它的父类是System.MulticastDelegate
看到这里,我的一个疑惑被解除了
以前我也自己定义过委托,总是感觉好像在程序中到处都可以定义委托,在类里面和类的外部都可以定义,一直不知道为什么?
现在我基本明白了,其实委托是一个类,类可以在哪里定义,委托就可以在哪里定义。
(2)我们现在再来看看构造器
在这之前,有必要搞清楚委托的继承关系
System.Delegate
---System.MulticastDelegate
------ConsoleApplication1.myEventHandler
现在我们实例化一个委托:
myEventHandler my=new myEventHandler(staticCall);
实际上它是在调用这个委托的构造器
下面的是这个构造器的代码:
public myEventHandler(object @object, IntPtr method);
第一个参数应该是类实例的实例
第二个参数应该是委托的方法信息
这个构造器会再调用它父类的构造器,父类构造器代码如下:
protected MulticastDelegate(object target, string method) : base(target, method)
{
}
最终调用的是System.Delegate的构造器
protected Delegate(object target, string method)
这个构造器是对类System.Delegate的2个私有字段进行处理
private object _target;
private RuntimeMethodInfo _method;
System.Delegate类有2个公有属性可以得到上述2个私有字段的数值:
1.Method
获取委托所表示的方法
2.Target
获取类实例,当前委托将对其调用实例方法
如果是静态方法,则为空引用
费话少说,我们来段代码看看效果
建立一个我最爱的控制台程序,代码如下:
1using System;
2using System.Reflection;
3
4namespace ConsoleApplication1
5{
6 class Class1
7 {
8 public delegate void myEventHandler(int i,out string o);
9 /** <summary>
10 /// 应用程序的主入口点。
11 /// </summary>
12 [STAThread]
13 static void Main(string[] args)
14 {
15 Class1 c=new Class1();
16 myEventHandler my=new myEventHandler(staticCall);
17
18 if (my.Target==null)
19 {
20 Console.WriteLine("我们调用的类静态方法");
21 }
22 else
23 {
24 Console.WriteLine("我们调用的类实例名称为:"+my.Target.ToString());
25 }
26
27 MemberInfo info=my.Method;
28
29 Console.WriteLine("此方法的基本信息:"+ my.Method.ToString() );
30 Console.WriteLine( my.Method.IsStatic?"此方法是一个静态方法":"此方法是一个实例方法" );
31 Console.WriteLine( "此方法的名称为:"+info.Name );
32
33 Console.ReadLine();
34 }
35
36 public void call(int i,out string o)
37 {
38 o="haha";
39 }
40
41
42 public static void staticCall(int i,out string o)
43 {
44 o="haha";
45 }
46 }
47}
48
我们可以看到结果
要是把代码中16行替换为
myEventHandler my=new myEventHandler(staticCall);
我们可以看到结果
(3)再来看看委托的虚方法BeginInvoke和虚方法EndInvoke
它的代码原型如下:
public virtual IAsyncResult BeginInvoke(int i, out string o, AsyncCallback callback, object @object);
public virtual void EndInvoke(out string o, IAsyncResult result);
BeginInvoke 异步方法签名的规则是:
包括所有 IN 参数。
包括所有 OUT 参数。
包括所有 IN/OUT 参数。
包括所有 ByRef 参数。
将 AsyncCallback 和 AsyncState(可通过 IAsyncResult 接口的 AsyncState 属性获得)作为最后两个参数。
返回 IAsyncResult
EndInvoke 异步方法签名的规则是:
包括所有 IN/OUT 参数。
包括所有 OUT 参数。
包括所有 ByRef 参数。
将 IAsyncResult 作为最后一个参数。
AsyncCallback委托,IAsyncResult接口,BeginInvoke方法,EndInvoke方法小结
让我们来看看同步异步的区别:
同步方法调用在程序继续执行之前需要等待同步方法执行完毕返回结果
异步方法则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作
.NET框架基类库中有好几种类都可以提供同步和异步的方法调用。
因为同步方法调用会导致程序流程中途等待,所以采用同步方法的情况下往往会导致程序执行的延迟
相比来说,在某些条件下选择异步方法调用就可能更好一些
例如,有的时候程序需要给多个Web服务发出请求,还有远程处理信道(HTTP、TCP)和代理,这时就最好采用异步方法
.NET Framework允许异步调用任何方法,定义与需要调用的方法具有相同签名的委托
CLR将自动为该委托定义添加适当签名的BeginInvoke虚方法和EndInvoke虚方法和Invoke方法。
我们先来了解这2个方法和一个委托和一个接口:
(1)
BeginInvoke方法用于启动异步调用
它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数,将 AsyncCallback 和 AsyncState(可通过 IAsyncResult 接口的
AsyncState 属性获得)作为最后两个参数,如没有可以为空.
BeginInvoke立即返回,不等待异步调用完成。
BeginInvoke返回IasyncResult,可用于监视调用进度。
结果对象IAsyncResult是从开始操作返回的,并且可用于获取有关异步开始操作是否已完成的状态。
结果对象被传递到结束操作,该操作返回调用的最终返回值。
在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。
(2)
EndInvoke方法用于检索异步调用结果。
在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke.
如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。
EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。
要注意的是,始终在异步调用完成后调用EndInvoke
(3)
AsyncCallback委托用于指定在开始操作完成后应被调用的方法
AsyncCallback委托被作为开始操作上的第二个到最后一个参数传递
代码原型如下:
[Serializable]
public delegate void AsyncCallback(IAsyncResult ar);
(4)
IAsyncResult接口
它表示异步操作的状态.
该接口定义了4个公用属性
实际上,发起和完成.NET异步调用有4种方案可供你选择
1.方案1-自己调用EndInvoke方法
异步执行方法的最简单方式是以BeginInvoke开始,对主线程执行一些操作,然后调用EndInvoke,EndInvoke直到异步调用完成后才返回
还是先来段自己喜欢的控制台代码:
1using System;
2
3namespace ConsoleApplication1
4{
5 class Class1
6 {
7 public delegate void AsyncEventHandler();
8
9 void Event1()
10 {
11 Console.WriteLine("Event1 Start");
12 System.Threading.Thread.Sleep(2000);
13 Console.WriteLine("Event1 End");
14 }
15
16 void Event2()
17 {
18 Console.WriteLine("Event2 Start");
19 int i=1;
20 while(i<1000)
21 {
22 i=i+1;
23 Console.WriteLine("Event2 "+i.ToString());
24 }
25 Console.WriteLine("Event2 End");
26 }
27
28 void CallbackMethod(IAsyncResult ar)
29 {
30 ((AsyncEventHandler) ar.AsyncState).EndInvoke(ar);
31 }
32
33
34 [STAThread]
35 static void Main(string[] args)
36 {
37 long start=0;
38 long end=0;
39 Class1 c = new Class1();
40 Console.WriteLine("ready");
41 start=DateTime.Now.Ticks;
42
43 AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
44 IAsyncResult ia=asy.BeginInvoke(null,null);
45 c.Event2();
46 asy.EndInvoke(ia);
47
48 end =DateTime.Now.Ticks;
49 Console.WriteLine("时间刻度差="+ Convert.ToString(end-start) );
50 Console.ReadLine();
51 }
52 }
53}
54
此程序简单,异步的处理过程在代码43-46这几行
结果如下:
现在让我们来看看同步处理
修改代码43-46这几行代码:
c.Event1();
c.Event2();
结果如下:
前者的时间刻度大大小于后者
我们可以明显地看到异步运行的速度优越性
2.方案2-采用查询(IsCompleted属性)
IAsyncResult.IsCompleted属性获取异步操作是否已完成的指示,发现异步调用何时完成.
再次修改代码43-46这几行代码:
AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
IAsyncResult ia=asy.BeginInvoke(null,null);
c.Event2();
while(!ia.IsCompleted)
{
}
asy.EndInvoke(ia);
3.方案3-采用AsyncWaitHandle来等待方法调用的完成
IAsyncResult.AsyncWaitHandle属性获取用于等待异步操作完成的WaitHandle
WaitHandle.WaitOne方法阻塞当前线程,直到当前的WaitHandle收到信号
使用WaitHandle,则在异步调用完成之后,但在通过调用EndInvoke结果之前,可以执行其他处理
再次修改代码43-46这几行代码:
AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
IAsyncResult ia=asy.BeginInvoke(null,null);
c.Event2();
ia.AsyncWaitHandle.WaitOne();
4.方案4-利用回调函数
如果启动异步调用的线程不需要处理调用结果,则可以在调用完成时执行回调方法
要使用回调方法,必须将代表该方法的AsyncCallback委托传递给BeginInvoke
再次修改代码43-46这几行代码:
AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
asy.BeginInvoke(new AsyncCallback(c.CallbackMethod),asy);
c.Event2();
异步调用示例1
好了。前面介绍了异步编程所用到的知识。现在写个异步例子是一件非常简单的事了。
异步调用示例: 直接调用EndInvoke 方法等待异步调用结束
// AsynCall1.cs
// 异步调用示例: 直接调用EndInvoke 方法等待异步调用结束
using System;
using System.Threading;
// 定义异步调用方法的委托
// 它的签名必须与要异步调用的方法一致
public delegate int AsynComputeCaller(ulong l, out ulong factorial);
public class Factorial
{
// 计算阶乘
public ulong Compute(ulong l)
{
// 不要太快 :-)
Thread.Sleep(50);
if (l == 1)
{
return 1;
}
else
{
return l * Compute(l - 1);
}
}
// 要异步调用的方法
// 1. 调用Factorial方法来计算阶乘,并用out参数返回
// 2. 统计计算阶乘所用的时间,并返回该值
public int AsynCompute(ulong l, out ulong factorial)
{
Console.WriteLine("开始异步方法");
DateTime startTime = DateTime.Now;
factorial = Compute(l);
TimeSpan usedTime = new TimeSpan(DateTime.Now.Ticks - startTime.Ticks);
Console.WriteLine("结束异步方法");
return usedTime.Milliseconds;
}
}
public class Test
{
public static void Main()
{
// 创建包含异步方法的类的实例
Factorial fact = new Factorial();
// 创建异步委托
AsynComputeCaller caller = new AsynComputeCaller(fact.AsynCompute);
// 启动异步调用
Console.WriteLine("启动异步调用");
ulong l = 30;
ulong lf;
IAsyncResult result = caller.BeginInvoke(l, out lf, null, null);
// 主线程进行一些操作
Thread.Sleep(0);
Console.WriteLine("主线程进行一些操作");
// 调用EndInvoke来等待异步调用结束,并获得结果
int returnValue = caller.EndInvoke(out lf, result);
// 异步调用的方法已经结束,显示结果
Console.WriteLine("已经得到结果:{0}的阶乘为{1},计算时间为{2}毫秒", l, lf, returnValue);
}
}
异步调用示例2
异步调用示例:使用 WaitHandle 等待异步调用结束
// AsynCall2.cs
// 异步调用示例:使用 WaitHandle 等待异步调用结束
using System;
using System.Threading;
// 定义异步调用方法的委托
// 它的签名必须与要异步调用的方法一致
public delegate int AsynComputeCaller(ulong l, out ulong factorial);
public class Factorial
{
// 计算阶乘
public ulong Compute(ulong l)
{
// 不要太快 :-)
Thread.Sleep(50);
if (l == 1)
{
return 1;
}
else
{
return l * Compute(l - 1);
}
}
// 要异步调用的方法
// 1. 调用Factorial方法来计算阶乘,并用out参数返回
// 2. 统计计算阶乘所用的时间,并返回该值
public int AsynCompute(ulong l, out ulong factorial)
{
Console.WriteLine("开始异步方法");
DateTime startTime = DateTime.Now;
factorial = Compute(l);
TimeSpan usedTime = new TimeSpan(DateTime.Now.Ticks - startTime.Ticks);
Console.WriteLine("结束异步方法");
return usedTime.Milliseconds;
}
}
public class Test
{
public static void Main()
{
// 创建包含异步方法的类的实例
Factorial fact = new Factorial();
// 创建异步委托
AsynComputeCaller caller = new AsynComputeCaller(fact.AsynCompute);
// 启动异步调用
Console.WriteLine("启动异步调用");
ulong l = 30;
ulong lf;
IAsyncResult result = caller.BeginInvoke(l, out lf, null, null);
// 主线程进行一些操作
Thread.Sleep(0);
Console.WriteLine("主线程进行一些操作");
// 等待WaitHandle接收到信号
Console.WriteLine("等待WaitHandle接收到信号");
result.AsyncWaitHandle.WaitOne();
// 主线程进行一些操作
Thread.Sleep(0);
Console.WriteLine("异步方法已经结束,主线程进行另外一些操作");
// 调用EndInvoke来获得结果
int returnValue = caller.EndInvoke(out lf, result);
// 异步调用的方法已经结束,显示结果
Console.WriteLine("已经得到结果:{0}的阶乘为{1},计算时间为{2}毫秒", l, lf, returnValue);
}
}
异步调用示例3
异步调用示例3: 轮询异步调用是否完成
// AsynCall3.cs
// 异步调用示例: 轮询异步调用是否完成
using System;
using System.Threading;
// 定义异步调用方法的委托
// 它的签名必须与要异步调用的方法一致
public delegate int AsynComputeCaller(ulong l, out ulong factorial);
public class Factorial
{
// 计算阶乘
public ulong Compute(ulong l)
{
// 不要太快 :-)
Thread.Sleep(50);
if (l == 1)
{
return 1;
}
else
{
return l * Compute(l - 1);
}
}
// 要异步调用的方法
// 1. 调用Factorial方法来计算阶乘,并用out参数返回
// 2. 统计计算阶乘所用的时间,并返回该值
public int AsynCompute(ulong l, out ulong factorial)
{
Console.WriteLine("开始异步方法");
DateTime startTime = DateTime.Now;
factorial = Compute(l);
TimeSpan usedTime = new TimeSpan(DateTime.Now.Ticks - startTime.Ticks);
Console.WriteLine("/n结束异步方法");
return usedTime.Milliseconds;
}
}
public class Test
{
public static void Main()
{
// 创建包含异步方法的类的实例
Factorial fact = new Factorial();
// 创建异步委托
AsynComputeCaller caller = new AsynComputeCaller(fact.AsynCompute);
// 启动异步调用
Console.WriteLine("启动异步调用");
ulong l = 30;
ulong lf;
IAsyncResult result = caller.BeginInvoke(l, out lf, null, null);
// 轮询异步方法是否结束
Console.WriteLine("主线程进行一些操作");
while (result.IsCompleted == false)
{
// 主线程进行一些操作
Thread.Sleep(10);
Console.Write(".");
}
// 主线程进行一些操作
Thread.Sleep(0);
Console.WriteLine("异步方法已经结束,主线程进行另外一些操作");
// 调用EndInvoke来获得结果
int returnValue = caller.EndInvoke(out lf, result);
// 异步调用的方法已经结束,显示结果
Console.WriteLine("已经得到结果:{0}的阶乘为{1},计算时间为{2}毫秒", l, lf, returnValue);
}
}
异步调用示例4
异步调用示例: 在异步调用完成时执行回调方法
// AsynCall4.cs
// 异步调用示例: 在异步调用完成时执行回调方法
using System;
using System.Threading;
// 定义异步调用方法的委托
// 它的签名必须与要异步调用的方法一致
public delegate int AsynComputeCaller(ulong l, out ulong factorial);
public class Factorial
{
// 计算阶乘
public ulong Compute(ulong l)
{
// 不要太快 :-)
Thread.Sleep(50);
if (l == 1)
{
return 1;
}
else
{
return l * Compute(l - 1);
}
}
// 要异步调用的方法
// 1. 调用Factorial方法来计算阶乘,并用out参数返回
// 2. 统计计算阶乘所用的时间,并返回该值
public int AsynCompute(ulong l, out ulong factorial)
{
Console.WriteLine("开始异步方法");
DateTime startTime = DateTime.Now;
factorial = Compute(l);
TimeSpan usedTime = new TimeSpan(DateTime.Now.Ticks - startTime.Ticks);
Console.WriteLine("结束异步方法");
return usedTime.Milliseconds;
}
}
public class Test
{
static ulong l = 30;
static ulong lf;
public static void Main()
{
// 创建包含异步方法的类的实例
Factorial fact = new Factorial();
// 创建异步委托
AsynComputeCaller caller = new AsynComputeCaller(fact.AsynCompute);
// 启动异步调用
Console.WriteLine("启动异步调用");
IAsyncResult result = caller.BeginInvoke(l, out lf,
new AsyncCallback(CallbackMethod), caller);
// 主线程进行一些操作
Thread.Sleep(0);
Console.WriteLine("主线程进行一些操作");
Console.WriteLine("按Enter键结束程序");
Console.ReadLine();
}
// 在异步调用完成时执行的回调方法
// 该回调方法的签名必须与AsyncCallback委托一致
static void CallbackMethod(IAsyncResult ar)
{
// 获取委托
AsynComputeCaller caller = (AsynComputeCaller)ar.AsyncState;
// 调用EndInvoke来获得结果
int returnValue = caller.EndInvoke(out lf, ar);
// 异步调用的方法已经结束,显示结果
Console.WriteLine("已经得到结果:{0}的阶乘为{1},计算时间为{2}毫秒", l, lf, returnValue);
}
}