65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 异步模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (26投票s)

2013年9月2日

CPOL

5分钟阅读

viewsIcon

123727

downloadIcon

2472

本文将介绍 .NET 中的异步模式及其实现方法。

介绍 

这并非一个新话题,但也绝不会过时。了解这个话题非常重要,理解越深入,你对 .NET 平台就掌握得越牢固。

如今,尤其是在 .NET 4.0 之后,异步编程已成为一种趋势,每个人都在追随。如果您构建了一个平台、框架或仅仅是一个类库,并且仍想对其进行支持,那么强烈建议为库中的每一个耗时方法都创建其异步对应版本。

背景  

本文是我之前关于类似主题文章的一次姗姗来迟的延续。

再次强调,无论您是编写 ASP.NET 应用程序、Windows 应用程序还是 .NET 程序集,并且希望利用异步功能,您都有许多方法和选项可供选择。

然而,本文并非关于发起异步调用的具体策略,而是关于设计应用程序的策略,因此,它关乎模式(Patterns)和模型(Models)。

正如 MSDN 文档所述,有三种模式可用于构建您的应用程序或重构您的库以使用异步功能:

异步编程模式

标题

描述

异步编程模型 (APM)

描述了使用 IAsyncResult 接口提供异步行为的传统模型。

此模型不再推荐用于新开发。

基于事件的异步模式 (EAP)

描述了提供异步行为的基于事件的传统模型。此模型不再推荐用于新开发。

基于任务的异步模式 (TAP) 

描述了基于 System.Threading.Tasks 命名空间的新异步模式。此模型是 .NET Framework 4 及更高版本中异步编程的推荐方法。

Using the Code

代码是有趣的部分,在我提供的示例中,我实现了这三种模式。

异步编程模型 (APM)

此模型**不再**推荐使用,并且已过时,但它非常简单且非常实用。
它适用于遗留系统,适用于 .NET 4.5 之前的任何系统,并且在您想要创建支持异步调用的 WCF 合同时也可能很有用。假设我们有一个耗时的方法,它接受两个整数参数并返回一个整数。

public int GetPrimeCount(int min, int count)
{
    PrintCurrentThreadId("GetPrimeCount");
    return ParallelEnumerable.Range(min, count).Count(n =>
        Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i =>
        n % i > 0));
}

异步编程模型依赖于以下内容:

  • 代表要调用的函数地址的委托。
  • 将调用功能分成 2 部分或开始/结束对。
  • 可选的回调函数。

这些是该模型的基础,但您可以做很多事情或以不同的方式塑造它。

public IAsyncResult BeginGetPrimeCount(int min, 
          int count, AsyncCallback callback, object userState)
{
    getPrimeCountCaller = this.GetPrimeCount;
    PrintCurrentThreadId("BeginGetPrimeCount");
    return getPrimeCountCaller.BeginInvoke(min, count, callback, userState);
}
public int EndGetPrimeCount(IAsyncResult result)
{
    PrintCurrentThreadId("EndGetPrimeCount");
    
    return getPrimeCountCaller.EndInvoke(result);
}

委托将如下所示:

private delegate int GetPrimeCountHandler(int min, int count);
private GetPrimeCountHandler getPrimeCountCaller;  

我对此模式的实现不需要我公开委托,也不需要我使用回调函数。它也不需要使用 IAsyncResult 及其 AsyncWaitHandle。

然而,正如您在 EndGetPrimeCount() 中所见,我使用了 getPrimeCountCaller 委托,但通过使用此行也可以避免:

((GetPrimeCountHandler)((AsyncResult)result).AsyncDelegate).EndInvoke(result);

这样做的目的是在另一个线程中调用该方法。

除非我们调用 EndInvoke(),否则我们无法获取结果,如果我们必须创建一个回调函数,则可以在回调函数中调用 EndInvoke()。

基于事件的异步模式 (EAP)

此模式也**不再**推荐使用,它可以以不同的方式实现,但它依赖于以下内容:

  • 您的方法应异步执行。
  • 执行结束后应触发一个事件。

为了实现这一点,您需要定义一个事件,该事件可以根据您的意愿进行设计,但它只需要传递结果的值。

对于这个例子,我选择了它看起来像这样:

public delegate void GetPrimeCountEventHandler(object sender, GetPrimeEventArg e);
public event GetPrimeCountEventHandler GetPrimeCount_Completed;

其中 GetPrimeEventArg 定义为:

public int GetPrimeCount(int min, int count)
{
   Program.PrintCurrentThreadId("GetPrimeCount");
   return ParallelEnumerable.Range(min, count).Count(n =>
         Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i =>
         n % i > 0));
}

您可以选择让它更简单。现在挑战在于进行异步调用,这可以通过多种方式完成,包括使用上面段落中显示的委托。此外,您可以使用 Thread, ThreadStart, Threadpool, BackgroundWorker,或任何您选择的其他技术。

对于这个例子,我选择了 ThreadPool,因为它比委托技术快得多,但我遇到了一些困难,因为 ThreadPool 只接受返回 void 并接受一个 object 类型参数的委托,而我的情况并非如此。

然而,我找到了一个在这里显示的解决方法:

public void GetPrimeCountAsync(int min, int count)
{
    int result = -1;
    ManualResetEvent evt = new ManualResetEvent(false);
    WaitCallback wait = new WaitCallback((x) =>
    {
        try
        {
            result = GetPrimeCount(min, count);
        }
        catch (Exception ex)
        {
            //ToDo handle the exception
        }
        finally
        {
            evt.Set();
        }
    });
    ThreadPool.QueueUserWorkItem(wait);
    evt.WaitOne();
    evt.Close();
    On_GetPrimeCount_Completed(new GetPrimeEventArg() 
      { Minimum = min, Count = count, Result = result });
}

该代码片段只是一个示例,它可以进行更多增强和完善。ThreadPool 将保证在单独的线程中执行,而 ManualResetEvent 将阻止该线程,直到您通过调用 Set() 方法手动释放它。

基于任务的异步模式 (TAP)

现在这是推荐使用的新模式,它仅在 .NET 4.5 中可用,要使用它,请参阅本文:  C# 5.0 中使用 async 和 await 进行异步编程

简而言之,您可以在任何用 async 关键字修饰的方法中使用 await

返回类型只能是 voidTaskTask<T>

这就足以使其变得 awaitable,这意味着您可以在任何其他 async 方法中使用 await 关键字调用它。

出于传统,方法名以 Async 结尾。

public async Task <int> GetPrimeCountAsync(int min, int count)
{
    return await Task.Run<int>(() =>
    {
        Program.PrintCurrentThreadId("GetPrimeCount");
        return ParallelEnumerable.Range(min, count).Count(n =>
            Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i =>
            n % i > 0));
    });
} 

这是推荐的模式,并且实现起来要容易得多,它也可以以不同的方式实现,但是,实现的关键在于使用 asyncawait 关键字,而框架将负责多线程。

性能非常出色。

值得关注的点  

异步编程可帮助您编写和构建健壮的应用程序和系统,它使您能够利用多线程能力并发挥机器的强大功能。.NET 在每个版本中都在不断简化。

结果示例:

 

© . All rights reserved.