当C#编译器处理委托类型时,它先自动产生一个派生自System.MulticasrDelegate的密封类,该类定义了三个公共方法:Invoke(),BeginInvoke()和EndInvoke()。Invoke()用来以同步方式调用委托对象维护的方法,而BeginInvoke()和EndInvoke()方法能在第二个执行线程上异步调用当前的方法。尽管.NET基础类库为多线程专门提供了整个命名空间System.Threading,而委托顺带就提供了这一功能,所有委托都能够异步调用成员,这也是.NET平台最大的优点。
1.委托的同步性
委托的Invoke()方法用来调用被代理对象以同步方式维护的方法。因此,调用委托的线程(比如应用程序的主线程)会一直等待,直到委托调用完成。以下示例是在同步模式下调用Add()方法,Main()方法会一直等到Add()方法调用结束后才输出操作结果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace SyncDelegate
{
//定义委托类型
public delegate int BinaryOp(int x, int y);
class Program
{
static int Add(int x, int y)
{
//输出正在执行中的线程ID
Console.WriteLine("Add() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Add() has been sleeping for {0} seconds.", i+1);
}
return x + y;
}
static void Main(string[] args)
{
Console.WriteLine("* * * * * Synch Delegate Review * * * * * ");
//输出正在执行中的线程ID
Console.WriteLine("Main() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
BinaryOp b = new BinaryOp(Add);
//也可以写成b.Invoke(20, 20),但是一般不直接调用Invoke方法
int answer = b(20, 20);
Console.WriteLine("Main() now is working!");
//获得计算结果
Console.WriteLine("20 + 20 is {0} !", answer);
Console.ReadKey();
}
}
}
在C#中,Invoke()方法并不会直接在代码中被调用,而是在使用“正常的”方法调用语法时在幕后被触发的。Thread.CurrentThread静态属性返回当前线程的引用,Thread.CurrentThread.ManagedThreadId属性返回线程的ID。正如运行结果所示,由于程序中所有的任务都被主线程执行,控制台中显示的是相同的ID值。结果如下:
2.委托的异步性
C#编译器处理delegate关键字的时候,其动态生成的类定义了两个方法BeginInvoke()和EndInvoke()。以定义的BinaryOp委托为例,其原型为:
public System.IAsyncResult BeginInvoke(int x, int y, System.AsyncCallback callback, object object);
public int EndInvoke(System.IAsyncResult result);
传入BeginInvoke()的参数,前几个参数必须符合定义的委托类型参数,最后两个参数必须是System.AsyncCallback和System.Object类型,同时返回类型为System.IAsyncResult。EndInvoke()的返回类型与定义的委托返回类型一致,而参数是System.IAsyncResult类型。
以下示例是以BianryOp委托异步调用Add()方法。
<span style="font-size:12px;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AsyncDelegate
{
class Program
{
public delegate int BinaryOp(int x, int y);
static int Add(int x, int y)
{
//输出正在执行中的线程ID
Console.WriteLine("Add() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
</span>
<span style="font-size:12px;"> for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Add() has been sleeping for {0} seconds.", i + 1);
}
return x + y;
}
static void Main(string[] args)
{
Console.WriteLine("* * * * * Async Delegate Invocation * * * * * ");
//输出正在执行中的线程ID
Console.WriteLine("Main() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
//在新线程中调用Add()
BinaryOp b = new BinaryOp(Add);
IAsyncResult iAR = b.BeginInvoke(10, 15, null, null);
Console.WriteLine("Main() now is working!");
//获得Add()方法的调用结果
int answer = b.EndInvoke(iAR);
Console.WriteLine("10 + 15 is {0}!", answer);
Console.ReadKey();
</span> }
}
}
运行结果如下:
2.1 System.IAsyncResult接口
BeginInvoke()返回的对象实现了IAsyncResult接口,而EndInvoke()需要一个IAsyncResult兼容类型作为它唯一地参数,这就允许线程在调用过BeginInvoke()方法之后,通过EndInvoke()方法获取调用结果。如果定义的委托没有返回值,则仅仅调用BeginInvoke()方法即可。
IAsyncResult接口提供了IsCompleted属性,使用这个属性,在调用EndInvoke()之前,便能判断异步调用是否真正完成。
<span style="font-size:12px;">namespace AsyncDelegateIsCompleted
{
class Program
{
public delegate int BinaryOp(int x, int y);
static int Add(int x, int y)
{
//输出正在执行中的线程ID
Console.WriteLine("Add() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Add() has been sleeping for {0} seconds.", i + 1);
}
return x + y;
}
static void AddCompleted(IAsyncResult itfAR)
{
Console.WriteLine("Add() is completed!");
}
static void Main(string[] args)
{
Console.WriteLine("* * * * * Async Delegate Invocation * * * * * ");
//输出正在执行中的线程ID
Console.WriteLine("Main() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
//在新线程中调用Add()
BinaryOp b = new BinaryOp(Add);
IAsyncResult iAR = b.BeginInvoke(10, 15, null, null);
while (iAR.IsCompleted == false)
{
Console.WriteLine("The thread which Add() in now is working!");
}
//获得Add()方法的调用结果
int answer = b.EndInvoke(iAR);
Console.WriteLine("10 + 15 is {0} !", answer);
Console.ReadKey();
}
}
}</span>
运行结果如下:
相对于IsCompleted属性,IAsyncResult接口提供了AsyncWaitHandle属性来实现更加灵活的等待逻辑。这个属性返回了一个WaitHandle类型的实例,该实例又公开了一个WaitOne()方法。使用WaitHandle. WaitOne()的方法的好处是可以指定最长的等待时间,如果超时返回false。
<span style="font-size:12px;">namespace AsyncDelegateIsCompleted
{
class Program
{
public delegate int BinaryOp(int x, int y);
static int Add(int x, int y)
{
//输出正在执行中的线程ID
Console.WriteLine("Add() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Add() has been sleeping for {0} seconds.", i + 1);
}
return x + y;
}
static void AddCompleted(IAsyncResult itfAR)
{
Console.WriteLine("Add() is completed!");
}
static void Main(string[] args)
{
Console.WriteLine("* * * * * Async Delegate Invocation * * * * * ");
//输出正在执行中的线程ID
Console.WriteLine("Main() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
//在新线程中调用Add()
BinaryOp b = new BinaryOp(Add);
IAsyncResult iAR = b.BeginInvoke(10, 15, null, null);
while (iAR.AsyncWaitHandle.WaitOne(1000, true) == false)
{
Console.WriteLine("The thread which Add() in now is working!");
}
//获得Add()方法的调用结果
int answer = b.EndInvoke(iAR);
Console.WriteLine("10 + 15 is {0} !", answer);
Console.ReadKey();
}
}
}</span>
运行结果如下:
2.2 AsyncCallback委托的作用
不通过轮询一个委托来确定异步调用方法是否执行结束,而是在任务完成时由次线程主动通知调用线程的方式会更好。如果想要实现这种方法,需要在调用BeginInvoke()时提供一个System.AsyncCallback委托类型的实例来作为参数,这个参数默认值为null。当异步调用完成的时候,委托便会自动调用该参数指定的方法。
public System.IAsyncResult BeginInvoke(int x, int y, System.AsyncCallback callback, object object);<span lang="EN-US" style="color: black;"><span style="font-family:Times New Roman;">
</span></span>
System.AsyncCallback委托类型的原型为:publicdelegate void AsyncCallback(IAsyncResult ar);
IAsyncResult输入参数被传入到AsyncCallback委托方法中。
如果想好去对分配在Main()中的BinaryOp委托对象的引用,只需把由AsyncDelegate属性返回的System.Object类型转换成BinaryOp类型就可以了。
2.3 BeginInvoke()方法的最后一个参数
异步委托的最后一个需要关注的地方就是BeginInvoke()方法的最后一个参数(默认值为null)。该参数允许从主线程传递额外的状态信息给回调方法。因为这个参数类型是System.Object,所以可以传入任何回调方法所希望的类型的数据。而在回调方法中,使用传入IAsyncResult参数的AsyncState属性就可以获得BeginInvoke()方法最后一个参数所传递的信息。
<span style="font-size:12px;">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace AsyncCallback
{
class Program
{
public delegate int BinaryOp(int x, int y);
static int Add(int x, int y)
{
//输出正在执行中的线程ID
Console.WriteLine("Add() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Add() has been sleeping for {0} seconds.", i + 1);
}
return x + y;
}
static void AddCompleted(IAsyncResult itfAR)
{
Console.WriteLine("AddCompleted() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
AsyncResult ar = (AsyncResult)itfAR;
BinaryOp b = (BinaryOp)ar.AsyncDelegate;
Console.WriteLine("10 + 15 is {0}!", b.EndInvoke(itfAR));
//需要类型强制转换
Console.WriteLine((string)(itfAR.AsyncState));
Console.WriteLine("Add() is completed!");
}
static void Main(string[] args)
{
Console.WriteLine("* * * * * Async Delegate Invocation * * * * * ");
//输出正在执行中的线程ID
Console.WriteLine("Main() is invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
//在新线程中调用Add()
BinaryOp b = new BinaryOp(Add);
IAsyncResult iAR = b.BeginInvoke(10, 15, new System.AsyncCallback(AddCompleted), "Main thanks AddCompleted!");
Console.ReadKey();
}
}
}</span>
运行结果为: