一尘不染

在ASP.NET MVC中触发并忘记异步方法

c#

一般的答案,如这里这里
发射后不管 的问题是不使用异步/ AWAIT,但使用Task.RunTaskFactory.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中启动并忘记异步方法?


阅读 329

收藏
2020-05-19

共1个答案

一尘不染

首先,让我指出在ASP.NET应用程序中“解雇”几乎总是一个错误。如果您不在乎是否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.Run(() => LogExceptionsAsync(() => DeleteFooAsync()));

另外,您可以保留TaskScheduler.UnobservedTaskException并像这样调用它:

BackgroundTaskManager.Run(() => DeleteFooAsync());
2020-05-19