C# asp.net mvc 中的触发和忘记异步方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18502745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 12:24:39  来源:igfitidea点击:

Fire and forget async method in asp.net mvc

c#asp.net.netasynchronousasync-await

提问by acarlon

The general answers such as hereand hereto fire-and-forgetquestions is not to use async/await, but to use Task.Runor TaskFactory.StartNewpassing in the synchronous method instead.
However, sometimes the method that I want to fire-and-forgetis async and there is no equivalent sync method.

一般的答案,如这里这里发射后不管的问题是不使用异步/ AWAIT,但使用Task.RunTaskFactory.StartNew在同步方式传递来代替。
但是,有时我想要即发即忘的方法是异步的,并且没有等效的同步方法。

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.

更新说明/警告:正如 Stephen Cleary 在下面指出的,在发送响应后继续处理请求是危险的。原因是因为 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.

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

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.

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

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.

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

So, the original question...

所以,原来的问题...

for example:

例如:

//External library
public async Task 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.

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

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

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.

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

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

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

UPDATE:

更新:

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:

导致形式异常:

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

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

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

这在这里讨论,似乎与 SynchronizationContext 和“在所有异步工作完成之前返回的任务已转换为终端状态”有关。

So, the only method that worked was:

因此,唯一有效的方法是:

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

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

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

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.

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

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

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

采纳答案by Stephen Cleary

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 DeleteFooAsyncactually completes.

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

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

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

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

And then use the BackgroundTaskManagerfrom my blog as such:

然后使用BackgroundTaskManager我博客中的:

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

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

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

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

回答by Scott Chamberlain

The best way to handle it is use the ContinueWithmethod and pass in the OnlyOnFaultedoption.

处理它的最佳方法是使用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");
}

Where I put my message box you would put your logger.

我把我的消息框放在哪里,你就会把你的记录器放在哪里。

回答by Korayem

As of .NET 4.5.2, you can do the following

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

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

But it only works within ASP.NET domain

但它只适用于 ASP.NET 域

The HostingEnvironment.QueueBackgroundWorkItem method lets you schedule small background work items. ASP.NET tracks these items and prevents IIS from abruptly terminating the worker process until all background work items have completed. This method can't be called outside an ASP.NET managed app domain.

HostingEnvironment.QueueBackgroundWorkItem 方法允许您安排小型后台工作项。ASP.NET 会跟踪这些项目并防止 IIS 在所有后台工作项目完成之前突然终止工作进程。无法在 ASP.NET 托管应用程序域外部调用此方法。

More here: https://msdn.microsoft.com/en-us/library/ms171868(v=vs.110).aspx#v452

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