异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。

.NET框架能够对任何方法进行异步调用。进行异步调用时,需要定义与异步调用的方法具有相同签名的委托。公共语言运行时会自动使用适当的签名为该委托定义BeginInvoke和EndInvoke方法。

BeginInvoke方法用于启动异步调用。它与需要异步执行的方法具有相同的参数。此外,它还有两个可选参数。第一个参数是一个AsyncCallback委托,该委托引用在异步调用完成时要调用的方法。第二个参数是一个用户定义的对象,该对象可向回调方法传递数据。BeginInvoke立即返回,不会等待异步调用完成,被调用的方法将在线程池线程中执行。因此,提交请求的原始线程与执行异步方法的线程池线程是并行执行的。BeginInvoke会返回一个IAsyncResult对象,可以使用该对象来监视异步调用进度,也可将该对象传递给EndInvoke方法,以获取异步执行的方法的返回值。

EndInvoke方法用于检索异步调用的结果。调用BeginInvoke方法后可随时调用EndInvoke方法;如果异步调用尚未完成,EndInvoke方法将一直阻塞调用线程,直到异步调用完成后才允许调用线程执行。EndInvoke方法的参数包括需要异步执行的方法的out和ref参数,以及由BeginInvoke返回的IAsyncResult对象。因此,通过EndInvoke方法可以获得异步调用的方法的所有输出数据,包括返回值、out和ref参数。

AsyncCallback委托表示在异步操作完成时调用的回调方法,其定义如下:

public delegate void AsyncCallback(IAsyncResult ar);

System.IAsyncResult接口表示异步操作的状态。IAsyncResult接口由包含可异步操作的方法的类实现。它是启动异步操作的方法的返回类型,也是结束异步操作的方法的第三个参数的类型。当异步操作完成时,IAsyncResult对象也将传递给由AsyncCallback委托调用的方法。支持IAsyncResult接口的对象存储异步操作的状态信息,并提供同步对象以允许线程在操作完成时终止。IAsyncResult接口定义了四个公开属性,通过它们可以获取异步调用的状态。

—  AsyncState:获取用户定义的对象,它限定或包含关于异步操作的信息。

—  AsyncWaitHandle:获取用于等待异步操作完成的 WaitHandle。

—  CompletedSynchronously:获取异步操作是否同步完成的指示。

—  IsCompleted:获取异步操作是否已完成的指示。

使用BeginInvoke和EndInvoke进行异步调用的常用方法主要有四种:

— 调用BeginInvoke方法启动异步方法,进行某些操作,然后调用EndInvoke方法来一直阻止请求线程到调用完成。

— 调用BeginInvoke方法启动异步方法,使用System.IAsyncResult.AsyncWaitHandle属性获取WaitHandle,使用它的WaitOne方法一直阻止执行直到发出WaitHandle信号,然后调用EndInvoke方法。

— 调用BeginInvoke方法启动异步方法,轮询由BeginInvoke返回的IAsyncResult,确定异步调用何时完成,然后调用EndInvoke。

— 调用BeginInvoke方法启动异步方法时,将代表异步方法完成时需要回调的方法的委托传递给BeginInvoke。异步调用完成后,将在ThreadPool线程上执行该回调方法。在该回调方法中调用EndInvoke。

每种方法都是通过BeginInvoke方法来启动异步方法,调用EndInvoke方法来完成异步调用。

1.直接调用EndInvoke 方法等待异步调用结束

异步执行方法的最简单的方式是通过调用委托的BeginInvoke方法来开始执行方法,在主线程上执行一些工作,然后调用委托的EndInvoke方法。EndInvoke可能会阻止调用线程,因为它直到异步调用完成之后才返回。这种技术非常适合于文件或网络操作,但是由于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 等待异步调用结束

可以使用BeginInvoke方法返回的IAsyncResult的AsyncWaitHandle属性来获取WaitHandle。异步调用完成时会发出WaitHandle信号,可以通过调用WaitOne方法来等待它。

如果使用WaitHandle,则在异步调用完成之前或之后,在通过调用EndInvoke检索结果之前,还可以执行其他处理。

下面的代码说明了如何使用这种方法来进行异步调用,并获得异步方法的结果:

// 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.轮询异步调用是否完成

可以使用由BeginInvoke方法返回的IAsyncResult的IsCompleted属性来发现异步调用何时完成。从用户界面的服务线程中进行异步调用时可以执行此操作。轮询完成允许调用线程在异步调用在线程池线程上执行时继续执行。

下面的代码说明了如何使用这种方法来进行异步调用,并获得异步方法的结果:

// 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.在异步调用完成时执行回调方法

如果启动异步调用的线程是不需要处理结果的线程,则可以在调用完成时执行回调方法。回调方法在线程池线程上执行。

若要使用回调方法,必须将引用回调方法的AsyncCallback委托传递给BeginInvoke。也可以传递包含回调方法将要使用的信息的对象。例如,可以传递启动调用时曾使用的委托,以便回调方法能够调用EndInvoke方法。

下面的代码说明了如何使用这种方法来进行异步调用,并获得异步方法的结果:

// 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);
    }
}