消防和忘记asp.net mvc的异步方法方法、asp、net、mvc

2023-09-02 11:45:53 作者:三分淑女七分浪

一般的答案,如这里 和的这里发射后忘了问题是不使用异步/计谋,而是利用Task.Run或TaskFactory.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.

更新注意/警告:作为斯蒂芬·克利里指出的下方,是很危险的继续工作的请求已发送的响应之后。其原因是因为在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关闭我将在远程系统的残余文件,但我会挑选起来为我平时的保养周期的一部分,因为它的存在是由我的网络服务器(数据库)不再是已知的,它的名字是唯一时间戳,也不会造成任何问题,同时它仍然阴魂不散。

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.

这将是理想的,如果我能够获得一个持久性机制,斯蒂芬·克利里指出的,可惜我没有在这个时候。

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.

我认为只是pretending的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的code我想打电话给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();
};

是否有这样做的任何风险?

Are there any risks in doing this?

这是我能想到的另一种选择是让我自己的包装,如:

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(可能是包裹在一个异步操作)。然而,这似乎是一个很多包装$ C $的三我想在火灾后不管的方式调用异步方法各一次。

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

出现异常的形式是:

caused an exception of the form:

未处理的异常: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)

这是讨论的here似乎是做了的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.

可悲的是,斯科特的建议,下面不处理异常在这种情况下工作,因为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.

如果你愿意接受这个限制,我有一些code在我的博客将注册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 从我的博客是这样的:

And then use the BackgroundTaskManager from my blog as such:

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

另外,您也可以保留 TaskScheduler.UnobservedTaskException 键,只需要调用它是这样的:

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

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