如果我有一个api控制器,并且希望它尽快返回给用户,同时分离出一个线程来处理更长的运行时间,我想我就不能等待这样的异步方法调用。
DoSomeNonAwaitedAsyncThing(data);
var result = await DoSomeNormalAsyncThing(data);
return result;
但是,当api控制器返回时,DoSomeNonAwaitedAsyncThing代码将停止执行(无异常或只是停止运行)。
如果我将DoSomeNonAwaitedAsyncThing的签名从异步任务更改为异步无效,则该线程将运行至完成状态,但也将阻止api调用返回,直到未完成的任务完成。
此外,如果我将调用方法的方式更改为this(下),则该方法将运行到完成而不是在方法中途停止。
Task.Run(async () => { DoSomeNonAwaitedAsyncThing(data); });
我创建了一个带有项目的仓库以演示该问题。 https://github.com/JamesWebDev/AsyncThreadIssueExample
同样值得注意的是,我将此添加到了Web配置中,以便它应允许在线程仍在运行时返回api调用。
<add key="aspnet:AllowAsyncDuringSyncStages" value="true"/>
答案 0 :(得分:1)
api控制器返回时,DoSomeNonAwaitedAsyncThing代码将停止执行
最有可能的原因是,DoSomeNonAwaitedAsyncThing
试图重新输入ASP.NET Classic请求上下文。默认情况下,await
will capture the current context and resume on that context;在ASP.NET Classic中,此“上下文”是ASP.NET Classic请求上下文。但是,由于该请求上下文所引用的请求已已完成,因此可能会导致...复杂化。
它与Task.Run
配合使用的原因是,DoSomeNonAwaitedAsyncThing
的当前上下文不再是ASP.NET Classic请求上下文;现在是线程池上下文。当await
中的DoSomeNonAwaitedAsyncThing
准备好继续时,线程池仍然存在。
作为最佳实践,您应该avoid fire-and-forget on ASP.NET(对于Classic和Core都是如此)。正确的解决方案是让您的控制器写入可靠的队列(例如,Azure队列),并让独立的后台进程处理该操作(例如,Azure Function)。独立背景处理还有其他选择-经典的HostingEnvironment.QueueBackgroundWorkItem
和IRegisteredObject
,Core的IHostedService
和IHostApplicationLifetime
-但最重要的是您在最低需要在控制器和后台进程之间建立可靠的队列。否则,您可能会在关机/循环期间失去工作。