【问题标题】:Proper way to start and async fire-and-forget call?启动和异步即发即弃呼叫的正确方法?
【发布时间】:2020-08-02 14:29:38
【问题描述】:

我有一个异步调用 (DoAsyncWork()),我想以一种即发即弃的方式开始,即我对它的结果不感兴趣,并且希望调用线程在异步之前继续方法结束。

这样做的正确方法是什么?我在 .NET Framework 4.6 和 .NETCore 2 中都需要这个,以防有差异。

public async Task<MyResult> DoWorkAsync(){...}

public void StarterA(){
    Task.Run(() => DoWorkAsync());
}

public void StarterB(){
    Task.Run(async () => await DoWorkAsync());
}

是这两者之一还是不同/更好的东西?

//edit:理想情况下没有任何额外的库。

【问题讨论】:

  • 你听说过挂火吗?
  • @EhsanSajjad 没有。不过,理想情况下,我想在没有任何额外库的情况下做到这一点。谢谢

标签: c# .net .net-core async-await task-parallel-library


【解决方案1】:

这样做的正确方法是什么?

首先,你需要确定你是否真的想要fire-and-forget。根据我的经验,大约 90% 提出此要求的人实际上想要一劳永逸。他们想要后台处理服务。

具体来说,即发即弃的意思是:

  1. 您不在乎操作何时完成。
  2. 你不关心执行操作时是否有任何异常。
  3. 您根本不在乎操作是否完成

因此,即发即弃的实际用例非常小。像更新服务器端缓存这样的操作就可以了。发送电子邮件、生成文档或任何与业务相关的事情可以,因为您会 (1) 希望操作完成,并且 (2) 在操作时收到通知出错了。

绝大多数时候,人们根本不想要一劳永逸;他们需要后台处理服务。构建其中一个的正确方法是添加一个可靠的队列(例如,Azure Queue / Amazon SQS,甚至是数据库),并拥有一个独立的后台进程(例如,Azure Function / Amazon Lambda / .NET Core BackgroundService / Win32服务)处理该队列。这基本上是 Hangfire 提供的(使用数据库作为队列,并在 ASP.NET 进程中运行后台进程 in-proc)。

是这两者之一还是不同/更好的东西?

一般情况下,there's a number of small behavior differences when eliding async and await。这不是您“默认”想要做的事情。

但是,在这种特定情况下 - async lambda 只调用一个方法 - 省略 asyncawait 很好。

【讨论】:

  • 感谢您的回答!实际上,我的情况是您提到的可以:更新缓存;)
【解决方案2】:

这取决于你的意思是什么:)

例如:您对在“即发即弃”调用中引发的异常感兴趣吗?如果没有,那还好。尽管您可能需要考虑的是任务所处的环境。

例如,如果这是一个 asp.net 应用程序,并且您在由于调用 .aspx 或 .svc 而实例化的线程的生命周期内执行此操作。任务成为该(前台)线程的后台线程。前台线程可能会在您的“即发即弃”任务完成之前被应用程序池清理掉。

所以还要考虑一下你的任务在哪个线程中运行。

我认为这篇文章为您提供了一些有用的信息: https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx

另外请注意,如果您没有在任务中返回值,则任务将不会返回异常信息。来源是微软考试 70-483 的参考书 网上某处可能有免费版本;P https://www.amazon.com/Exam-Ref-70-483-Programming-C/dp/0735676828

可能有用的是,如果您有一个由非异步调用的异步方法并且您希望知道它的结果。您可以使用 .GetAwaiter().GetResult()。

另外我认为注意异步和多线程之间的区别很重要。

仅当存在使用计算机的其他部分而非 CPU 的操作时,异步才有用。因此,诸如网络或 I/O 操作之类的事情。然后使用 async 会告诉系统继续并在其他地方使用 CPU 电源,而不是“阻塞”CPU 中的该线程以等待响应。

多线程是在 CPU 中的不同线程上分配操作(例如,创建一个任务来创建前台线程的后台线程...前台线程是构成您的应用程序的线程,它们是主要的,后台线程存在链接到前台线程。如果关闭链接的前台线程,后台线程也会关闭) 这使得 CPU 可以同时处理不同的任务。

如果是 4 线程 CPU,将这两者结合可确保 CPU 不会仅在 4 个线程上被阻塞。但可以在等待等待 I/O 操作的异步任务时打开更多。

我希望这能为您提供所需的信息,无论您在做什么 :)

【讨论】:

  • 感谢您的见解!是的,我对任何异常都不感兴趣(DoWorkAsync 中有异常处理)。是的,所有这些都在 ASP.NET 应用程序中。这会改变你的答案吗?
  • 不是真的,你可以跳过关于返回值的部分。那就不需要了 :) 但我确实想强调前台线程中有关后台线程的部分。我的公司决定使用“一劳永逸”的方法在 aspx 页面上的线程生命周期内对 Azure 应用程序洞察进行额外调用。并且很惊讶没有分配记录。那是因为该任务是在该页面的操作结束之前实例化的。这意味着线程和刚刚创建的任务都被应用程序池清理了。所以请看一下我在上面的链接
  • 谢谢。对于 ASP.NET 部分,我将使用 Scotts 帖子中的 queuebackgroundworkitem