在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器。