如何解决从同步操作方法中调用带有 await 的异步方法
我需要实现一个启动处理的服务。但我不需要等待结果。我可以只显示默认输出,并在后台运行该进程。
但是我遇到了await之后的代码没有执行的问题。
我准备了一些代码来展示这个想法:
//GEN-BEGIN //GEN-END
我知道它看起来很糟糕。但这个想法是这样的:
- 一个线程转到死锁方法。
- 线程同步转到 AsyncCall 方法。
- 面对 await 语句。
- 从死锁方法开始。
- 继续 main 方法到最后。
- Task.Delay 完成后,该线程将从线程池中出现并继续工作。
我的坏 6 步未处理。我尝试设置断点但从未被击中。
但如果我减少时间延迟并在调试中进行,我将进入第 6 步。
Enter to the controller's action method
After return from controller's action method
但是如果我在await后只留下一个断点,我就不会走到第6步public class HomeController : Controller
{
public ActionResult Deadlock()
{
AsyncCall();
return View();
}
private async Task AsyncCall()
{
await Task.Delay(10000);
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
private void Do(string m)
{
m.Contains("x");
}
}
注意:
我将 var nonreachablePlace = "The breakpoint will not set the execution here";
附加到 ConfigureAwait(false)
。它看起来像这样:
Task.Delay()
现在它按预期工作了。
我的问题是为什么没有 ConfigureAwait(false) 的代码不能工作?
也许它与 private async Task AsyncCall()
{
await Task.Delay(10000).ConfigureAwait(false);
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
有某种关系,并且在主线程完成其工作后无法访问。然后等待方法尝试在已经处理的情况下获取上下文(只是我的想法)
解决方法
使用HostingEnvironment.QueueBackgroundWorkItem
。
请注意,这仅在 .NET Framework (System.Web.dll
) 上的 Classic ASP.NET 中可用,而在 ASP.NET Core 中不可用(我忘记了它在 ASP.NET Core 中的工作范围1.x 和 2.x 在 .NET Framework 上运行,但无论如何。
你只需要这个:
using System.Web.Hosting;
public class MyController : Controller
{
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem( this.DoSomethingExpensiveAsync ); // Pass the method by name or as a `Func<CancellationToken,Task>` delegate.
return this.View();
}
private async Task DoSomethingExpensiveAsync( CancellationToken cancellationToken )
{
await Task.Delay( TimeSpan.FromSeconds( 30 ) );
}
}
您还可以将其用于非异步工作负载:
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem( this.DoSomethingExpensive ); // Pass the method by name or as a `Action<CancellationToken>` delegate.
return this.View();
}
private void DoSomethingExpensive( CancellationToken cancellationToken )
{
Thread.Sleep( 30 * 1000 ); // NEVER EVER EVER call Thread.Sleep in ASP.NET!!! This is just an example!
}
如果你想一开始正常开始工作,如果时间太长只在后台完成,那么这样做:
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
Task<String> workTask = this.DoSomethingExpensiveAsync( default );
Task timeoutTask = Task.Delay( TimeSpan.FromSeconds( 5 ) );
Task first = await Task.WhenAny( workTask,timeoutTask );
if( first == timeoutTask )
{
// `workTask` is still running,so resume it in the background:
HostingEnvironment.QueueBackgroundWorkItem( async ct => await workTask );
return this.View( "Still working..." );
}
else
{
// `workTask` finished before the timeout:
String result = await workTask; // or just `workTask.Result`.
return this.View( result );
}
}
private async Task<String> DoSomethingExpensiveAsync( CancellationToken cancellationToken )
{
await Task.Delay( TimeSpan.FromSeconds( 30 ) );
return "Explosive bolts,ten thousand volts; At a million miles an hour,Abrasive wheels and molten metals";
}
,
所以 Deadlock()
调用 AsyncCall()
然后AsyncCall()
告诉DeadLock()
“好吧,我在等Task.Delay
数到10,000,但你可以继续。”
...所以 AsyncCall()
将主线程返回到 DeadLock()
。
现在DeadLock()
从来没有说过要等待AsyncCall()
完成,所以就DeadLock()
而言,AsyncCall()
已经返回了(实际上只是屈服了,但是程序游标仍会被传回 DeadLock()
。
所以我建议在 AsyncCall()
中的 DeadLock()
方法中设置断点 ,因为您可能会看到您的主线程已经完成并在 { {1}} 甚至已经完成。
所以Task.Delay()
甚至都没有机会完成AsyncCall()
。
我深入研究了 Task.Delay(10000)
背后的逻辑以及之后的继续代码。
感谢 Stephen Toub 制作的 post。
主要问题部分在于任务完成时。并且它的结果需要由下一个线程处理。
由于我没有编写 ConfigureAwait()
,因此我隐含地打算在具有 SynchronizationContext
(在我的情况下为 AspNetSynchronizationContext
)的线程中运行代码。
private async Task AsyncCall()
{
/// The delay is done by a thread from a ThreadPool.
await Task.Delay(10000);
/// After the Task has been finished
/// TaskAwaiter tryies to send a continuation code to a thread with
/// SynchronizationContext.
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
因为不想等待 awaitable 的结果,所以返回了控制器 action 的响应。然后线程转到 ThreadPool 并处理 SynchronizationContext。 到任务完成的那一刻,没有 SynchronizationContext 发送带有继续代码的委托。
在代码创建过程中,Visual Studio Enabled Just My Code
选项设置为 true。这就是为什么这个异常被默默地抛出给我。
还有关于即使我有 Task.Delay(2000)
也能运行代码的情况。我认为这是由 Classic ASP.NET 完成请求并创建对它的响应所需的时间造成的。在此期间,您可以获得对 SynchronizationContext
的引用和对它的 Post
委托。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。