异步编程之Async,Await和ConfigureAwait的关系

在.NET Framework 4.5中,async / await关键字已添加到该版本中,简化多线程操做,以使异步编程更易于使用。为了最大化利用资源而不挂起UI,你应该尽量地尝试使用异步编程。虽然async / await让异步编程更简单,可是有一些你可能不知道的细节和注意的地方
 数据库

新关键字

微软在.NET框架中添加了async和await关键字。可是,使用它们,方法的返回类型应为Task类型。(咱们将在稍后讨论例外状况)为了使用await关键字,您必须在方法定义中使用async。若是你在方法定义中放入async,你应该在主体方法的某个地方至少有一处await关键字,若是你缺乏他,你一般会收到Visual Studio的一个警告。
如下是代码中的示例:编程

 public async Task ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
        {
            using (var context = _contextFactory.Create())
            {
                var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
                // Mapping logic
                await context.SaveChangesAsync(token);
            }
        }

 

若是要从异步方法返回某些内容,可使用Task的泛型。像如下这样(若是你想返回受影响的行数)
 
public async Task<int> ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
        {
            using (var context = _contextFactory.Create())
            {
                var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
                // Mapping logic
                return await context.SaveChangesAsync(token);
            }
        }

 

async.await给咱们带来了什么?

虽然使用这个看起来很简单,可是它有什么帮助呢?最后,全部这些操做都是在等待数据库返回结果时(在本例中)让其余请求使用当前线程。当您向数据库、磁盘、internet等外部源发出可能须要一段时间才能运行的请求时,咱们可使用async/ wait让其余请求使用这个线程。这样,咱们就不会有空闲的“worker”(线程)在那里等待完成其余任务。这就像去快餐店同样,在你点完菜以后,其余人不会点任何东西,直到你吃完为止。使用async/ await,其余人能够在你点完菜以后下他们的订单,而且能够同时处理多个订单。安全

 

它不能作什么?

这里须要注意的一件事是async/await并非并行/多核编程。当您使用async/await时,只处理该线程,并让其余线程使用它。代码的做用相似于“同步”,由于您能够在await以后以本方法继续执行代码。所以,若是在一个方法中有四个await,则必须等到每一个方法都完成后才能调用下一个方法。所以,您必须使用任务库或任何您喜欢的方法生成新线程,以使它们并行运行。可是,您也可让每一个线程使用async/wait,这样它们就不会阻塞资源了!多线程

 

ConfigureAwait(false)能作什么呢?

默认状况下,当您使用async/await时,它将在开始请求的原始线程上继续运行(状态机)。可是,若是当前另外一个长时间运行的进程已经接管了该线程,那么你就不得不等待它完成。要避免这个问题,可使用ConfigureAwait的方法和false参数。当你用这个方法的时候,这将告诉Task它能够在任何可用的线程上恢复本身继续运行,而不是等待最初建立它的线程。这将加快响应速度并避免许多死锁。

可是,这里有一点点损失。当您在另外一个线程上继续时,线程同步上下文将丢失,由于状态机改变。这里最大的损失是你会失去归属于线程的Culture和Language,其中包含了国家语言时区信息,以及来自原始线程的HttpContext.Current之类的信息,所以,若是您不须要以此来作多语系或操做任何HttpContext类型设置,则能够安全地进行此方法的调用。注意:若是须要language/culture,能够始终在await以前存储当前相关状态值,而后在await新线程以后从新应用它。app

 
如下是ConfigureAwait(false)的示例:框架

  
  public async Task<int> ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
        {
            using (var context = _contextFactory.Create())
            {
                var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
                // Mapping logic
                return await context.SaveChangesAsync(token).CongifureAwait(false);
            }
        }

 

注意事项

同步 -->异步

若是要使用async/await,须要注意一些事情。您可能遇到的最大问题是处理异步方法请求同步方法。若是你开发一个新项目,一般能够将async/await从上到下贯穿于整个方法链中,而不须要作太多工做。可是,若是你在外层是同步的,而且必须调用异步库,那么就会出现一些有隐患的操做。若是一不当心,便会引起大批量的死锁

若是有同步方法调用异步方法,则必须使用ConfigureAwait(false)。若是不这样作,就会当即掉进死锁陷阱。发生的状况是主线程将调用async方法,最终会阻塞这个线程,直到那个async方法完成。然而,一旦异步方法完成,它必须等待原始调用者完成后才能继续。他们都在等待对方完成,并且永远不会。经过在调用中使用configurewait (false), async方法将可以在另外一个线程上完成本身操做,而不关心本身的状态机的位置,并通知原始线程它已经完成。进行这个调用的最佳实践以下:异步

[HttpPut]
public IActionResult Put([FromBody]UpdateCommand command) =>
    _responseMediator.ExecuteAsync(command).ConfigureAwait(false).GetAwaiter().GetResult();

 

 

.NET Standard与ConfigureAwait(false)

在.NETCore中,微软删除了致使咱们在任何地方都须要ConfigureAwait(false)的SynchronizationContext。所以,ASP.NETCore应用程序在技术上不须要任何ConfigureAwait(false)逻辑,由于它是多余的。可是,若是在开发有一个使用.NETStandard的库,那么强烈建议仍然使用.ConfigureAwait(false)。在.NETCore中,这自动是无效的。可是若是有.NETFramework的人最终使用这个库并同步调用它,那么它们将会遇到一堆麻烦。可是随着.NET5是由.NETCore构建的,因此将来大多都是.NetCore调用.Netstadard,你若是不许备让.NetFramework调用你的standard库,大可没必要兼容。
 async

ConfigureAwait(false) 贯穿始终

若是同步调用有可能调用您的异步方法,那么在整个调用堆栈的每一个异步调用上,您都将被迫设置. configureAwait (false) !若是不这样作,就会致使另外一个死锁。这里的问题是,每一个async/ await对于调用它的当前方法都是本地的。所以,调用链的每一个异async/await均可能最终在不一样的线程上恢复。若是一个同步调用一路向下,遇到一个没有configurewait(false)的任务,那么这个任务将尝试等待顶部的原始线程完成,而后才能继续。虽然这最终会让你感到心累,由于要检查全部调用是否设置此属性。
 异步编程

开销

虽然async/ await能够极大地增长应用程序一次处理的请求数量,可是使用它是有代价的。每一个async/ await调用最终都将建立一个小状态机来跟踪全部信息。虽然这个开销很小,可是若是滥用async/ await,则会致使速度变慢。只有当线程不得不等待结果时,才应该等待它。
 
性能

Async Void

虽然几乎全部的async / await方法都应返回某种类型的Task,但此规则有一个例外:有时,您可使用async void。可是,当您使用它时,调用者实际上不会等待该任务完成后才能恢复本身。它其实是一种即发即忘的东西。有两种状况你想要使用它。 
 
第一种状况是事件处理程序,如WPF或WinForms中的按钮单击。默认状况下,事件处理程序的定义必须为void。若是你把一个任务放在那里,程序将没法编译,而且返回某些东西的事件会感受很奇怪。若是该按钮调用异步async,则必须执行async void才能使其正常工做。幸运的是,这是咱们想要的,由于这种使用不会阻塞UI。 
 
第二个是请求你不介意等待得到结果的东西。最多见的示例是发送日志邮件,但不想等待它完成或者不关心它是否完成。 
 
然而,对于这两种状况,都有一些缺点。首先,调用方法不能try/catch调用中的任何异常。它最终将进入AppDomain UnhandledException事件。不过,若是在实际的async void方法中放入一个try catch,就能够有效地防止这种状况发生。另外一个问题是调用者永远不会知道它什么时候结束,由于它不返回任何东西。所以,若是你关心何时完成某个Task,那么实际上须要返回一个Task。
 

 

探讨.NetCore中异步注意事项

在.NetCore中已经剔除了SynchronizationContext,剔除他的主要缘由主要是性能和进一步简化操做

在.NetCore中咱们不用继续关心异步同步混用状况下,是否哪里没有设置ConfigureAwait(false) 会致使的死锁问题,由于在.netcore中的async/await 可能在任何线程上执行,而且可能并行运行!

如下代码为例:

private HttpClient _client = new HttpClient();

async Task<List<string>> GetBothAsync(string url1, string url2)
{
    var result = new List<string>();
    var task1 = GetOneAsync(result, url1);
    var task2 = GetOneAsync(result, url2);
    await Task.WhenAll(task1, task2);
    return result;
}

async Task GetOneAsync(List<string> result, string url)
{
    var data = await _client.GetStringAsync(url);
    result.Add(data);
}

 

它下载两个字符串并将它们放入一个List中。此代码在旧版ASP.NET(.NetFramework)中工做正常,因为请求处设置了await,请求上下文一次只容许一个链接.

其中result.Add(data)一次只能由一个线程执行,由于它在请求上下文中执行。

可是,这个相同的代码在ASP.NET Core上是不安全的; 具体地说,该result.Add(data)行能够由两个线程同时执行,而不保护共享List<string>

因此在.Netcore中要特别注意异步代码在并行执行状况下引起的问题

 

参考:https://stackoverflow.com/questions/31186354/async-await-where-is-continuation-of-awaitable-part-of-method-performed

相关文章
相关标签/搜索