【问题标题】:what are the circumstances of using .Wait() when calling async methods调用异步方法时使用 .Wait() 的情况是什么
【发布时间】:2015-12-15 18:53:57
【问题描述】:

我的 asp.net mvc-5 Web 应用程序中有以下 async 长时间运行方法:-

 public async Task<ScanResult> ScanAsync(string FQDN)
 {
    // sample of the operation i am doing 
    var c = await context.SingleOrDefaultAsync(a=>a.id == 1);
    var list = await context.Employees.ToListAsync();
    await context.SaveChangesAsync();
    //etc..
}

我正在使用支持运行后台作业的 Hangfire 工具及时调用此异步方法,但不幸的是,hangefire 工具不支持直接调用异步方法。所以为了克服这个问题,我创建了上述方法的同步版本,如下:-

public void Scan()
{
    ScanAsync("test").Wait();
}

然后我从 HangFire 调度程序调用同步方法如下:-

RecurringJob.AddOrUpdate(() => ss.Scan(), Cron.Minutely);

所以我知道在方法执行期间使用.Wait()将主要占用iis线程,但是正如我提到的,我需要这样做,因为我不能直接在hangefire调度程序中调用异步TASK。

那么当我使用.Wait() 调用异步方法时会发生什么?整个方法的操作会以同步方式完成吗?例如,如上所示,我在ScanAsync() ;SingleOrDefualtAsync,ToListAsyncSaveChangesAsync 中有三个异步操作,所以它们会以同步方式执行,因为我正在使用 .wait() 调用 ScanAsync 方法?

【问题讨论】:

  • 不能这样称呼吗? RecurringJob.AddOrUpdate(async () =&gt; await ss.ScanAsync(), Cron.Minutely);
  • 我认为您需要做的一件事是检查任务是否有异常。像这样:var task = ScanAsync("test"); task.Wait(); if(task.Exception != null) {throw task.Texception}; 如果你不这样做,你可能不会发现你的后台工作是否有异常。
  • @Igor 不,Hangfire 特别不支持直接执行异步方法。见this issue
  • @Igor 它会引发一个错误,在这种情况下是 Startupclass,其中“RecurringJob.AddOrUpdate(async () => await ss.ScanAsync(), Cron.Minutely);”定义将需要是一个异步任务,它不起作用,因为我不能在非异步任务方法中使用等待

标签: c# asynchronous asp.net-mvc-5 async-await entity-framework-6


【解决方案1】:

那么当我使用 .Wait() 调用异步方法时会发生什么? 整个方法的操作会以同步的方式完成吗?例如 如上所示,我在 ScanAsync() 中有三个异步操作 ;SingleOrDefualtAsync、ToListAsync 和 SaveChangesAsync,它们也会如此 以同步方式执行,因为我正在调用 ScanAsync 方法使用 .wait() ?

查询数据库的方法仍将异步执行,但您调用Wait 的事实意味着即使您正在释放线程,它也不会在您暂停时返回到 ASP.NET ThreadPool它。

这也可能导致死锁,因为 ASP.NET 有一个自定义同步上下文,可确保在继续异步调用时请求的上下文可用。

我建议您改为使用实体框架提供的同步 API,因为您实际上不会享受异步调用所能获得的可扩展性。

编辑:

在 cmets 中,你问:

正如我目前使用hangefire 所做的那样,消除异步效果?如果是,那么使用同步方法会更好吗?或者使用 hangeffire 的同步或异步将完全相同

首先,您必须了解异步的好处。你这样做不是因为它很酷,你这样做是因为它有一个目的。那是什么目的?能够在负载下缩放。它是如何做到的?当您 await 异步方法时,控制权将交还给调用者。例如,您有一个传入请求,您查询您的数据库。您可以坐在那里等待查询完成,也可以重新使用该线程来处理更多传入的请求。这才是真正的力量。

如果您实际上不打算接收大量请求(这样您会饿死线程池),那么您实际上不会看到异步带来的任何好处。目前,按照您实现它的方式,您不会看到任何这些好处,因为您正在阻止异步调用。您可能看到的只是死锁。

【讨论】:

  • 我不清楚(我认为 John 也不确定)在 Hangfire 环境中的重要性,Hangfire 本身将处理执行传递的 lambda。
  • @Yuval 您能否详细说明“我建议您改用实体框架提供的同步 API,因为您实际上不会享受可扩展性”可以从异步调用中获得。” ??
  • @mason Hangfire 仍然在 IIS/ASP.NET 下运行并消耗线程。如果 Hangfire 能够使用异步方法,它可以在等待数据库时将它们释放到池中。事实上,使用同步方法意味着编译器创建的代码稍微简单一些,因为它不必生成实现async/await所需的代码
  • @johnG #3 如果代码仅由 Hangfire 使用。在 Hangfire 本身引入异步支持之前,使用异步代码没有任何好处。 #1 如果ScanAsync 也被控制器方法调用(可能不是一个好主意)。 #2 只会破坏 Hangfire 及其仪表板。您将无法看到正在运行的作业或在必要时取消它们,因为作业将立即完成并留下一个孤立的任务运行
  • @johnG 他们永远不会完全一样。至少,异步代码更复杂,因为编译器必须创建一个状态机,所以开销更大。不是很大,所以除非你真的需要资源,否则你可能不必担心。
【解决方案2】:

这在很大程度上取决于HangFire 的实现方式。如果它只是将要在ThreadPool 中调用的任务排队,那么唯一的效果是,您的一个线程将被阻塞,直到请求结束。但是,如果有自定义 SynchronizationContext,这可能会导致严重的死锁。

考虑一下,如果您真的想等待安排好的工作完成。也许你想要的只是一个火而忘记的模式。这样你的方法将是这样的:

public void Scan()
{
    ScanAsync("test"); // smoothly ignore the task
}

如果您确实需要等待,您可以尝试使用async void 方法:

public async void Scan()
{
    await ScanAsync("test");
    DoSomeOtherJob();
}

关于使用async void有很多争议,因为你不能等待这个方法结束,也不能处理可能的错误。

但是,在事件驱动的应用程序中,这可能是唯一的方法。更多信息可以参考:Async Void, ASP.Net, and Count of Outstanding Operations

【讨论】:

  • Web 应用程序不是事件驱动的。事实上,Stephen Cleary 在那个问题中说 *don't use async void`。无论如何,在 ASP.NET 中运行长时间运行的作业需要大量的管道,你不能只使用async void,因为它可能会在主请求完成处理后立即终止
  • @Kedrzu 所以你的意思是如果我简单地删除 .Wait() 那么我会避免或最大限度地减少死锁的可能性吗?
  • 您所说的“顺利忽略任务”的意思是“我不在乎任务是否失败”。这很可能不是这种情况。您的第一个代码将引入一个静默失败的条件。而且没有“DoSomeOtherJob”,因为这个Scan 方法纯粹是ScanAsync 的同步包装器。
  • 这是最初在 ASP 应用程序中使用作业调度程序的问题。作业调度程序是事件驱动的。也许真正的解决方案是把它从 ASP 中扔掉 :)
  • @mason 是的,这是完全正确的。我认为真正的问题是,这是否是一个问题。也许不时不做一些经常性的工作并不是什么大不了的事。这将取决于要求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-06
  • 2018-03-11
  • 2018-05-15
  • 2016-11-23
相关资源
最近更新 更多