在实际的编程中某些任务执行完成时间可能较长,比如打开大文件、连接远程计算机或查询数据库,这个时候如果采用异步操作可以极大提高程序的运行效率,提供良好用户体验。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序仍然可以继续执行当前的程序。
下面列举了.NET Framework 中支持异步编程的部分,主要包括: 文件(File) IO、流(Stream) IO、套接字(Socket) IO,网络,远程处理信道(HTTP、TCP)和代理,使用 ASP.NET 创建的 XML Web services,ASP.NET Web 窗体,使用 MessageQueue 类的消息队列等。
谈到异步操作,就不得不说异步委托,可以说异步委托在实现异步操作方面可谓有得天独厚的优势。那么什么是异步委托呢?异步委托就是定义一个方法,开一个新线程,让这个方法在后台执行。委托是方法的类型安全引用。Delegate类支持异步调用的方法,在后台Delegate类会创建一个执行任务的线程。
在.NET平台下,主要通过委托的BeginInvoke()来实现异步操作。
下面我会先通过一个小例子给大家进行解析。
首先创建一个委托
Public delegate void SayHello(string name);
然后在程序的主函数里写下如下代码:
static void Main(string[] args)
{
Console.WriteLine("主线程的ID:" + Thread.CurrentThread.ManagedThreadId);//标记显示主线程ID
SayHello sayhello = new SayHello(Say);//新建一个委托对象
sayhello.BeginInvoke("Olive", null, null);//调用委托的异步函数BeginInvoke(),这里边三个参数,第一个参数,为所调用的函数传递的参数,第二、第三个会在下边慢慢介绍
private static void Say(string name)
{
Console.WriteLine("Hello!--------" + name);
Console.WriteLine("当前的线程ID为:" + Thread.CurrentThread.ManagedThreadId);//显示当前函数执行时所在线程
}
实验结果如下:
通过这个简单的小例子我们可以发现:通过委托所实现的异步,不过是又启用了一个新的线程,但是大家要注意的是这个线程是线程池里的线程,是属于后台线程的。
下面我们继续,刚刚我们做的例子是不需要返回结果的异步操作,而实际的开发中,往往需要有返回的结果。那该如何做呢?
首先我们还是要先建一个委托:
delegate string SayHello(string name);
在主函数中代码如下:
static void Main(string[] args)
{
Console.WriteLine("主线程的ID:" + Thread.CurrentThread.ManagedThreadId);//标记显示主线程ID
SayHello sayhello = new SayHello(Say);
IAsyncResult iResult=sayhello.BeginInvoke("Olive", null, null);//Delegate.BeginInvoke()方法实际上是一个IAsyncResult的对象
string result = sayhello.EndInvoke(iResult);//Delegate.EndInvoke()方法就是返回调用函数的返回值,但是该方法需要一个IAsyncResult对象,也就是委托调用BeginInvoke()开始异步
Console.WriteLine(result);
Console.ReadKey();
}
private static string Say(string name)
{
Console.WriteLine("Hello!--------" + name);
Console.WriteLine("当前的线程ID为:" + Thread.CurrentThread.ManagedThreadId);
return "I Love you " + name;
}
实验结果如下:
但是这个时候又有一个问题出现了,刚刚我们所举的异步操作只是很简单的返回一句话而已,如果是非常耗时的异步操作的话,我们也不知道操作有没有结束,如果在未结束时调用Delegate.EndInvoke()会一直的处在阻塞状态。这个时候该怎么办呢?
可以有三种办法解决这个问题:
第一个就是反复的询问
只需要在主函数里做如下修改即可:
static void Main(string[] args)
{
Console.WriteLine("主线程的ID:" + Thread.CurrentThread.ManagedThreadId);//标记显示主线程ID
SayHello sayhello = new SayHello(Say);
IAsyncResult iResult=sayhello.BeginInvoke("Olive", null, null);//Delegate.BeginInvoke()方法实际上是一个IAsyncResult的对象
If(iResult.IsCompleted)
{
string result = sayhello.EndInvoke(iResult);//Delegate.EndInvoke()方法就是返回调用函数的返回值,但是该方法需要一个IAsyncResult对象,也就是委托调用BeginInvoke()开始异步
Console.WriteLine(result);
}
Console.ReadKey();
}
第二个方法:使用IAsyncReault相关联的等待句柄,使用AsyncWaitHandle属性就可以访问等待句柄。这个属性返回WaitHandle类型的对象,它可以等待委托线程完成其任务。WaitOne()方法将一个超时时间作为可选的第一个参数,在其中可以定义要等待的最长时间。如果发生超时WaitOne方法就返回false
if (iResult.AsyncWaitHandle.WaitOne(1000,false)) //如果等待超过1秒,则无法返回结果
{
string result = sayhello.EndInvoke(iResult);
Console.WriteLine(result);
}
第三种方法:异步回调。这个方法是最为推荐的方法。
那么什么是异步回调呢?或许刚刚大家有注意到在调用Delegate.BeginInvoek()方法时,只有有三个参数BeginInvoke(object parameter,AsyncCallback asynccallback,object state),上面只讲第一个参数,也就是委托调用的有参函数所需的参数,多个参数也可以,只需要按照委托声明时的顺序依次添加就可以了,第二个是一个AsyncCallBack类型的委托也就是异步操作执行完后要执行的委托函数,比如需要返回结果、输出什么的都可以在这个委托函数里执行,该委托函数必须有一个IAsyncResult类型的参数。第三个是额外的状态参数,一般都是该委托对象。
下面通过实例给大家进行讲解。
第一步还是先建立一个委托
delegate string SayHello(string name);
主函数:
static void Main(string[] args)
{
Console.WriteLine("主线程的ID:" + Thread.CurrentThread.ManagedThreadId);//标记显示主 线程ID
SayHello sayhello = new SayHello(Say);
IAsyncResult iResult=sayhello.BeginInvoke("Olive",new AsyncCallback(Result), sayhello);//三个参数:1、Say()函数的参数,2、AsyncCallback类型的委托,(异步操作结束后执行的委托函数),3、将sayhello对象作为状态参数,在Result函数中会有用到该参数
Console.ReadKey();
}
//AsyncCallback委托所执行的函数
private static void Result(IAsyncResult iasyncresult)
{
SayHello sayhello = (SayHello)iasyncresult.AsyncState;//获取IAsyncResult对象的AsyncState的属性,即为Delegate.BeginInvoke()的第三个参数即sayhello。
string s = sayhello.EndInvoke(iasyncresult);
Console.WriteLine(s + "------好了,异步调用到这里已经结束了");
}
private static string Say(string name)
{
Console.WriteLine("Hello!--------" + name);
Console.WriteLine("当前的线程ID为:" + Thread.CurrentThread.ManagedThreadId);
return "I Love you " + name;
}
实验结果如图:
至此,本节的异步操作已经结束了,在实际的运用中异步操作用的还是比较多的,建议大家在使用异步操作的时候多用下异步回调,这种方法效率比较高、也不会发生异步的阻塞。