诸如此处和此处之类的 即发即弃问题的一般答案不是使用异步/等待,而是使用Task.Run
或TaskFactory.StartNew
传入同步方法。
但是,有时我想要一劳永逸的方法是异步的,并且没有等效的同步方法。
更新说明/警告:正如 Stephen Cleary 在下面指出的,在您发送响应后继续处理请求是危险的。原因是 AppDomain 可能在该工作仍在进行时被关闭。有关更多信息,请参阅他的回复中的链接。无论如何,我只是想提前指出这一点,这样我就不会让任何人走错路。
我认为我的情况是有效的,因为实际工作是由不同的系统(不同服务器上的不同计算机)完成的,所以我只需要知道消息已留给该系统。如果出现异常,服务器或用户无能为力并且不会影响用户,我需要做的就是参考异常日志并手动清理(或实施一些自动化机制)。如果 AppDomain 被关闭,我将在远程系统中有一个残留文件,但我会在我通常的维护周期中选择它,因为我的 Web 服务器(数据库)不再知道它的存在并且它的名称是唯一的时间戳,它不会导致任何问题,而它仍然徘徊。
如果我可以访问 Stephen Cleary 指出的持久性机制,那将是理想的,但不幸的是,我现在没有。
我考虑只是假装 DeleteFoo 请求已在客户端(javascript)上正常完成,同时保持请求打开,但我需要响应中的信息才能继续,所以它会阻止事情。
所以,原来的问题...
例如:
//External library
public async Task DeleteFooAsync();
在我的 asp.net mvc 代码中,我想以一种即发即弃的方式调用 DeleteFooAsync - 我不想等待响应等待 DeleteFooAsync 完成。如果 DeleteFooAsync 由于某种原因失败(或抛出异常),用户或程序对此无能为力,所以我只想记录一个错误。
现在,我知道任何异常都会导致未观察到的异常,所以我能想到的最简单的情况是:
//In my code
Task deleteTask = DeleteFooAsync()
//In my App_Start
TaskScheduler.UnobservedTaskException += ( sender, e ) =>
{
m_log.Debug( "Unobserved exception! This exception would have been unobserved: {0}", e.Exception );
e.SetObserved();
};
这样做有什么风险吗?
我能想到的另一个选择是制作自己的包装器,例如:
private void async DeleteFooWrapperAsync()
{
try
{
await DeleteFooAsync();
}
catch(Exception exception )
{
m_log.Error("DeleteFooAsync failed: " + exception.ToString());
}
}
然后使用 TaskFactory.StartNew 调用它(可能包含在异步操作中)。然而,每次我想以一种即发即弃的方式调用异步方法时,这似乎是很多包装代码。
我的问题是,以即发即弃的方式调用异步方法的正确方法是什么?
更新:
好吧,我在我的控制器中发现了以下内容(并不是说控制器操作需要异步,因为还有其他异步调用正在等待):
[AcceptVerbs( HttpVerbs.Post )]
public async Task<JsonResult> DeleteItemAsync()
{
Task deleteTask = DeleteFooAsync();
...
}
导致形式异常:
未处理的异常:System.NullReferenceException:对象引用未设置为对象的实例。在 System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext)
这在此处进行了讨论,似乎与 SynchronizationContext 和“在所有异步工作完成之前返回的任务已转换为终端状态”有关。
因此,唯一有效的方法是:
Task foo = Task.Run( () => DeleteFooAsync() );
我对为什么会这样的理解是因为 StartNew 为 DeleteFooAsync 获得了一个新线程来处理。
遗憾的是,Scott 的以下建议不适用于在这种情况下处理异常,因为 foo 不再是 DeleteFooAsync 任务,而是来自 Task.Run 的任务,因此不处理来自 DeleteFooAsync 的异常。我的 UnobservedTaskException 最终确实会被调用,所以至少它仍然有效。
所以,我想这个问题仍然存在,你如何在 asp.net mvc 中执行一劳永逸的异步方法?