一般的答案,如这里和这里 来 发射后不管 的问题是不使用异步/ AWAIT,但使用Task.Run或TaskFactory.StartNew在同步方式传递来代替。 但是,有时我要 触发并忘记的方法 是异步的,并且没有等效的同步方法。
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在所有异步工作完成之前已转换为终端状态”。
因此,唯一有效的方法是:
Task foo = Task.Run( () => DeleteFooAsync() );
我之所以知道这是为什么,是因为StartNew为DeleteFooAsync获得了一个新的线程。
可悲的是,Scott的以下建议在这种情况下不适用于异常处理,因为foo不再是DeleteFooAsync任务,而是Task.Run中的任务,因此不再处理DeleteFooAsync的异常。我的UnobservedTaskException最终确实会被调用,因此至少它仍然有效。
因此,我想问题仍然存在,如何在asp.net mvc中启动并忘记异步方法?
首先,让我指出在ASP.NET应用程序中“解雇”几乎总是一个错误。如果您不在乎是否DeleteFooAsync真正完成操作,那么“开火即忘”只是可接受的方法。
DeleteFooAsync
如果您愿意接受该限制,那么我的博客上有一些代码可以在ASP.NET运行时中注册任务,并且可以接受同步和异步工作。
您可以编写一次性包装器方法来记录异常,如下所示:
private async Task LogExceptionsAsync(Func<Task> code) { try { await code(); } catch(Exception exception) { m_log.Error("Call failed: " + exception.ToString()); } }
然后BackgroundTaskManager像这样从我的博客中使用:
BackgroundTaskManager
BackgroundTaskManager.Run(() => LogExceptionsAsync(() => DeleteFooAsync()));
另外,您可以保留TaskScheduler.UnobservedTaskException并像这样调用它:
TaskScheduler.UnobservedTaskException
BackgroundTaskManager.Run(() => DeleteFooAsync());