在asp.net mvc中火和忘记asynchronous方法

一般的回答,比如在这里和这里给大家的问题不是使用asynchronous/等待,而是使用Task.Run或者TaskFactory.StartNew传入同步方法。
但是,有时候我想忘记的方法是asynchronous的,没有等效的同步方法。

更新注意/警告:正如Stephen Cleary指出的那样,在您发送回应之后继续处理请求是很危险的。 原因是因为AppDomain可能会在该作业仍在进行时closures。 请参阅他的回应链接了解更多信息。 无论如何,我只是想提前指出,这样我就不会让任何人走错路。

我认为我的情况是有效的,因为实际的工作是由不同的系统(不同的服务器上的不同的计算机)完成的,所以我只需要知道消息已经离开了那个系统。 如果发生exception,那么服务器或用户就无法做到这一点,也不会影响用户,所有我需要做的就是引用exception日志并手动清除(或实现一些自动化机制)。 如果AppDomain被closures,我将在远程系统中有一个残留文件,但是我将把它作为通常的维护周期的一部分,并且由于它的存在不再被我的Web服务器(数据库)所知,并且它的名字是唯一的时间戳,它不会导致任何问题,而它仍然徘徊。

如果我能够像斯蒂芬·克里里指出的那样使用持久性机制,那将是理想的,但是不幸的是我现在不在这个时候。

我认为只是假装DeleteFoo请求在客户端(JavaScript)完成罚款,同时保持请求打开,但我需要在响应中的信息继续,所以它会举办。

所以,原来的问题…

例如:

 //External library public async Task DeleteFooAsync(); 

在我的asp.net mvc代码中,我想以一种即忘即逝的方式调用DeleteFooAsync – 我不想阻止等待DeleteFooAsync完成的响应。 如果由于某种原因,DeleteFooAsync失败(或引发exception),那么用户或程序就不能执行任何操作,所以我只想logging一个错误。

现在,我知道任何exception都会导致不可观察的exception,所以我能想到的最简单的情况是:

 //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(); }; 

这样做有没有风险?

我能想到的另一个select是制作自己的包装,例如:

 private void async DeleteFooWrapperAsync() { try { await DeleteFooAsync(); } catch(Exception exception ) { m_log.Error("DeleteFooAsync failed: " + exception.ToString()); } } 

然后用TaskFactory.StartNew调用它(可能包装在一个asynchronous操作中)。 然而,这似乎是每一次我想打电话给一个asynchronous的方法在一个消防和忘记的方式很多包装代码。

我的问题是,以一种难以忘怀的方式调用asynchronous方法的正确方法是什么?

更新:

那么,我发现在我的控制器(不是控制器操作需要是asynchronous的,因为有其他asynchronous调用正在等待):

 [AcceptVerbs( HttpVerbs.Post )] public async Task<JsonResult> DeleteItemAsync() { Task deleteTask = DeleteFooAsync(); ... } 

引起了一个exception的forms:

未处理的exception:System.NullReferenceException:未将对象引用设置为对象的实例。 在System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext)

这里讨论了这一点,似乎是与SynchronizationContext有关,而且“在所有asynchronous工作完成之前返回的任务已经转换到terminal状态”。

所以,唯一有效的方法是:

 Task foo = Task.Run( () => DeleteFooAsync() ); 

我的理解为什么这个工作是因为StartNew得到一个新的线程DeleteFooAsync工作。

不幸的是,Scott的build议在这种情况下不适用于处理exception,因为foo不再是DeleteFooAsync任务,而是来自Task.Run的任务,所以不能处理来自DeleteFooAsync的exception。 我UnobservedTaskException最终会被调用,所以至less仍然有效。

所以,我想这个问题依然存在,你如何在asp.net mvc中实现一个asynchronous方法?

首先,让我指出在ASP.NET应用程序中几乎总是出现“忘记和遗忘”的错误。 如果你不关心DeleteFooAsync是否实际完成,那么“Fire and forget”只是一个可接受的方法。

如果你愿意接受这个限制,我在我的博客上有一些代码将注册ASP.NET运行时的任务,它接受同步和asynchronous的工作。

您可以编写一个一次性的包装器方法来loggingexception,例如:

 private async Task LogExceptionsAsync(Func<Task> code) { try { await code(); } catch(Exception exception) { m_log.Error("Call failed: " + exception.ToString()); } } 

然后从我的博客中使用BackgroundTaskManager

 BackgroundTaskManager.Run(() => LogExceptionsAsync(() => DeleteFooAsync())); 

或者,您可以保留TaskScheduler.UnobservedTaskException ,只是这样调用它:

 BackgroundTaskManager.Run(() => DeleteFooAsync()); 

从.NET 4.5.2开始,您可以执行以下操作

 HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync()); 

但它只能在ASP.NET域中使用

HostingEnvironment.QueueBackgroundWorkItem方法可让您安排小型后台工作项目。 ASP.NET跟踪这些项目并防止IIS突然终止工作进程,直到完成所有后台工作项目。 此方法不能在ASP.NET托pipe的应用程序域之外调用。

更多信息: https : //msdn.microsoft.com/en-us/library/ms171868(v=vs.110).aspx#v452

处理它的最好方法是使用ContinueWith方法并传入OnlyOnFaulted选项。

 private void button1_Click(object sender, EventArgs e) { var deleteFooTask = DeleteFooAsync(); deleteFooTask.ContinueWith(ErrorHandeler, TaskContinuationOptions.OnlyOnFaulted); } private void ErrorHandeler(Task obj) { MessageBox.Show(String.Format("Exception happened in the background of DeleteFooAsync.\n{0}", obj.Exception)); } public async Task DeleteFooAsync() { await Task.Delay(5000); throw new Exception("Oops"); } 

在哪里我把我的消息框,你会把你的logging器。