在 asp.net mvc 中触发并忘记异步方法 [英] Fire and forget async method in asp.net mvc

查看:25
本文介绍了在 asp.net mvc 中触发并忘记异步方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一般答案,例如此处这里即发即弃的问题不是使用 async/await,而是使用 Task.RunTaskFactory.StartNew 而是传入同步方法.
但是,有时我想要触发并忘记的方法是异步的,并且没有等效的同步方法.

The general answers such as here and here to fire-and-forget questions is not to use async/await, but to use Task.Run or TaskFactory.StartNew passing in the synchronous method instead.
However, sometimes the method that I want to fire-and-forget is async and there is no equivalent sync method.

更新说明/警告:正如 Stephen Cleary 在下面指出的,在发送响应后继续处理请求是危险的.原因是因为 AppDomain 可能在该工作仍在进行时关闭.有关更多信息,请参阅他的回复中的链接.无论如何,我只是想预先指出这一点,以免让任何人走上错误的道路.

Update Note/Warning: As Stephen Cleary pointed out below, it is dangerous to continue working on a request after you have sent the response. The reason is because the AppDomain may be shut down while that work is still in progress. See the link in his response for more information. Anyways, I just wanted to point that out upfront, so that I don't send anyone down the wrong path.

我认为我的案例是有效的,因为实际工作是由不同的系统(不同服务器上的不同计算机)完成的,所以我只需要知道消息已离开该系统.如果出现异常,服务器或用户对此无能为力并且不影响用户,我需要做的就是参考异常日志并手动清理(或实现一些自动化机制).如果 AppDomain 关闭,我将在远程系统中保留一个残留文件,但我会将其作为日常维护周期的一部分进行提取,因为我的 Web 服务器(数据库)不再知道它的存在,并且它的名称是唯一的时间戳,它不会导致任何问题,而它仍然存在.

I think my case is valid because the actual work is done by a different system (different computer on a different server) so I only need to know that the message has left for that system. If there is an exception there is nothing that the server or user can do about it and it does not affect the user, all I need to do is refer to the exception log and clean up manually (or implement some automated mechanism). If the AppDomain is shut down I will have a residual file in a remote system, but I will pick that up as part of my usual maintenance cycle and since its existence is no longer known by my web server (database) and its name is uniquely timestamped, it will not cause any issues while it still lingers.

如果我可以使用 Stephen Cleary 指出的持久性机制,那将是理想的,但不幸的是,我目前没有.

It would be ideal if I had access to a persistence mechanism as Stephen Cleary pointed out, but unfortunately I don't at this time.

我考虑过只是假装 DeleteFoo 请求在客户端 (javascript) 上完成得很好,同时保持请求打开,但我需要响应中的信息才能继续,所以它会阻止事情.

I considered just pretending that the DeleteFoo request has completed fine on the client side (javascript) while keeping the request open, but I need information in the response to continue, so it would hold things up.

所以,原来的问题...

So, the original question...

例如:

//External library
public async Task DeleteFooAsync();

在我的 asp.net mvc 代码中,我想以即发即忘的方式调用 DeleteFooAsync - 我不想阻止响应等待 DeleteFooAsync 完成.如果 DeleteFooAsync 由于某种原因失败(或抛出异常),则用户或程序对此无能为力,因此我只想记录错误.

In my asp.net mvc code I want to call DeleteFooAsync in a fire-and-forget fashion - I don't want to hold up the response waiting for DeleteFooAsync to complete. If DeleteFooAsync fails (or throws an exception) for some reason, there is nothing that the user or the program can do about it so I just want to log an error.

现在,我知道任何异常都会导致无法观察到的异常,所以我能想到的最简单的情况是:

Now, I know that any exceptions will result in unobserved exceptions, so the simplest case I can think of is:

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

这样做有什么风险吗?

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

The other option that I can think of is to make my own wrapper such as:

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

然后使用 TaskFactory.StartNew 调用它(可能包含在异步操作中).但是,每次我想以即发即忘的方式调用异步方法时,这似乎都有很多包装器代码.

and then call that with TaskFactory.StartNew (probably wrapping in an async action). However this seems like a lot of wrapper code each time I want to call an async method in a fire-and-forget fashion.

我的问题是,以一劳永逸的方式调用异步方法的正确方法是什么?

My question is, what it the correct way to call an async method in a fire-and-forget fashion?

更新:

好吧,我发现我的控制器中有以下内容(不是控制器操作需要异步,因为还有其他异步调用在等待):

Well, I found that the following in my controller (not that the controller action needs to be async because there are other async calls that are awaited):

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

导致表单异常:

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

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext)

这是讨论 此处 似乎与 SynchronizationContext 和在所有异步工作完成之前返回的任务已转换为终端状态"有关.

This is discussed here and seems to be to do with the SynchronizationContext and 'the returned Task was transitioned to a terminal state before all async work completed'.

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

So, the only method that worked was:

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

我对它为什么有效的理解是因为 StartNew 为 DeleteFooAsync 获取了一个新线程.

My understanding of why this works is because StartNew gets a new thread for DeleteFooAsync to work on.

遗憾的是,Scott 下面的建议在这种情况下不适用于处理异常,因为 foo 不再是 DeleteFooAsync 任务,而是来自 Task.Run 的任务,因此不会处理来自 DeleteFooAsync 的异常.我的 UnobservedTaskException 最终会被调用,所以至少它仍然有效.

Sadly, Scott's suggestion below does not work for handling exceptions in this case, because foo is not a DeleteFooAsync task anymore, but rather the task from Task.Run, so does not handle the exceptions from DeleteFooAsync. My UnobservedTaskException does eventually get called, so at least that still works.

所以,我想问题仍然存在,您如何在 asp.net mvc 中执行即发即忘的异步方法?

So, I guess the question still stands, how do you do fire-and-forget an async method in asp.net mvc?

推荐答案

首先,让我指出即发即弃"在 ASP.NET 应用程序中几乎总是一个错误.如果您不关心 DeleteFooAsync 是否真正完成,即发即弃"只是一种可接受的方法.

First off, let me point out that "fire and forget" is almost always a mistake in ASP.NET applications. "Fire and forget" is only an acceptable approach if you don't care whether DeleteFooAsync actually completes.

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

If you're willing to accept that limitation, I have some code on my blog that will register tasks with the ASP.NET runtime, and it accepts both synchronous and asynchronous work.

您可以编写一个一次性包装器方法来记录异常:

You can write a one-time wrapper method for logging exceptions as such:

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 并像这样调用它:

Alternatively, you can keep TaskScheduler.UnobservedTaskException and just call it like this:

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

这篇关于在 asp.net mvc 中触发并忘记异步方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆