C# 从同步操作方法调用包含wait的异步方法
我需要实现一个启动处理的服务。但我不需要等待结果。我可以只显示默认输出,在后台进程将正常运行。C# 从同步操作方法调用包含wait的异步方法,c#,.net,asp.net-mvc,multithreading,async-await,C#,.net,Asp.net Mvc,Multithreading,Async Await,我需要实现一个启动处理的服务。但我不需要等待结果。我可以只显示默认输出,在后台进程将正常运行。 但是我遇到了一个问题,wait之后的代码没有执行 我准备了一些代码来展示这个想法: public class HomeController : Controller { public ActionResult Deadlock() { AsyncCall(); return View(); } private async Task A
但是我遇到了一个问题,wait之后的代码没有执行 我准备了一些代码来展示这个想法:
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");
}
}
我知道它看起来很糟糕。但当时的想法是:
但是如果我在等待后只留下一个断点,我就不会进入第6步
var nonrechableplace=“断点不会在此处设置执行”代码>
注:
我将ConfigureAwait(false)
附加到Task.Delay()
中。看起来是这样的:
private async Task AsyncCall()
{
await Task.Delay(10000).ConfigureAwait(false);
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
现在它的工作原理与预期一致
我的问题是,如果没有ConfigureWait(false),代码为什么不能工作?
可能它与同步上下文有关,并且在主线程完成其工作后无法访问它。在这个等待的方法之后,尝试在上下文已经被释放时获取上下文(我的想法是这样的)SoDeadlock()
callsAsyncCall()
然后AsyncCall()
告诉DeadLock()
“好的,我正在等待任务。延迟计数到10000,但您可以继续。”
…因此AsyncCall()
将主线程返回到DeadLock()
现在,DeadLock()
从未说过要等待AsyncCall()
完成,所以就DeadLock()
而言,AsyncCall()
已经返回了(它实际上只是产生了,但程序游标仍然会传递回DeadLock()
)
因此,我建议在DeadLock()
中的AsyncCall()
方法处设置断点,因为您可能会看到在Task.Delay()
完成之前,主线程已经完成并退出
因此,AsyncCall()
甚至没有机会完成等待的使用HostingEnvironment.QueueBackgroundWorkItem
请注意,这仅在.NET Framework上的经典ASP.NET(System.Web.dll
)中可用,而在ASP.NET Core中不可用(我忘记了它在.NET Framework上运行的ASP.NET Core 1.x和2.x中的工作范围,但无论如何)
您所需要的就是:
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 ) );
}
}
使用System.Web.Hosting;
公共类MyController:Controller
{
[HttpPost(“/foo”)]
公共异步任务DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem(this.doSomethingeExpensiveAsync);//按名称或作为'Func'委托传递方法。
返回这个.View();
}
专用异步任务DoSomethingeExpensiveAsync(CancellationToken CancellationToken)
{
等待任务延迟(时间跨度从秒(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”)]
公共异步任务DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem(this.doSomethingeExpensive);//按名称或作为'Action'委托传递方法。
返回这个.View();
}
私有void DoSomethingExpensive(CancellationToken CancellationToken)
{
Thread.Sleep(30*1000);//永远不要在ASP.NET中调用Thread.Sleep!!!这只是一个例子!
}
如果您想在开始时正常启动作业,并且只在需要较长时间的情况下在后台完成作业,请执行以下操作:
[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";
}
[HttpPost(“/foo”)]
公共异步任务DoSomething()
{
Task workTask=this.DoSomethingExpensiveAsync(默认值);
任务超时任务=任务延迟(TimeSpan.FromSeconds(5));
任务优先=等待任务。WhenAny(工作任务,超时任务);
if(first==超时任务)
{
//'workTask'仍在运行,请在后台继续运行:
HostingEnvironment.QueueBackgroundWorkItem(异步ct=>Wait workTask);
返回此。视图(“仍在工作…”);
}
其他的
{
//`workTask`在超时之前完成:
String result=await workTask;//或仅“workTask.result”。
返回此.视图(结果);
}
}
专用异步任务DoSomethingeExpensiveAsync(CancellationToken CancellationToken)
{
等待任务延迟(时间跨度从秒(30));
返回“爆炸螺栓,一万伏特;以每小时一百万英里的速度,磨轮和熔化的金属”;
}
我深入研究了任务背后的逻辑。延迟(10000)
以及之后的继续代码。
多亏了Stephen Toub的作品。
主要问题部分是当任务完成时。它的结果需要由下一个线程处理。
由于我没有编写ConfigureAwait()
,我暗指在具有SynchronizationContext
(AspNetSynchronizationContext
)的线程中运行代码
因为我不想等待等待的结果,所以我从控制器的操作返回了响应。然后线程转到ThreadPool,SynchronizationContext被释放。
到任务完成时,还没有SynchronizationContext可以发送带有延续代码的委托。
在代码创建过程中,Visual Studio仅启用了“我的代码”
选项设置为true
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);
}