启动和使用异步调用的正确方法是什么?

3

我有一个异步调用 (DoAsyncWork()),我希望以“fire-and-forget”的方式启动它,即我对其结果不感兴趣,并且希望在异步方法完成之前调用线程继续执行。

如何正确地做到这一点?如果有差异,我需要在.NET Framework 4.6和.NET Core 2中都实现。

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

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

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

这是其中之一还是其他更好的选择?编辑:最好不要使用额外的库。

你听说过Hangfire吗? - Ehsan Sajjad
@EhsanSajjad 不,理想情况下我希望不使用任何额外的库。谢谢。 - silent
2个回答

6

这该怎么做才是正确的方法?

首先,你需要决定是否真正需要fire-and-forget。根据我的经验,大约90%的人实际上不需要fire-and-forget;他们需要一个后台处理服务。

具体来说,fire-and-forget意味着:

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

因此,fire-and-forget的现实用例非常有限。像更新服务器端缓存这样的操作是可以的。发送电子邮件、生成文档或任何与业务相关的操作都不可行,因为你希望操作完成,并在操作出现错误时得到通知。

绝大多数情况下,人们根本不需要“发射并忘记”,而是需要后台处理服务。构建此类服务的正确方法是添加可靠的队列(例如 Azure Queue / Amazon SQS,或者甚至是数据库),并使用独立的后台进程(例如 Azure Function / Amazon Lambda / .NET Core BackgroundService / Win32 service)处理该队列。这基本上就是 Hangfire 所提供的(使用数据库作为队列,并在 ASP.NET 进程中运行后台进程)。

是其中两个还是其他不同/更好的东西?

一般情况下,省略 asyncawait 时有一些小的行为差异。这不是你想“默认”做的事情。

然而,在这种特定情况下 - 当 async lambda 只调用单个方法时 - 省略 asyncawait 是可以的。


1
谢谢你的回答!事实上,我的情况就是你提到的可以的那种情况:更新缓存 ;) - silent
当然,在某些情况下,这是一个很好的建议,但也有很多情况下,"点火并忘记"是一个合适的设计,因为操作与核心用户请求无关,不需要用户等待,也不应该在该上下文中生成错误。这个回答回答了问题"我应该使用点火并忘记吗?"而不是"我如何实现点火并忘记?"这是异步工具中的一种合法工具,当然,读者有责任决定是否适当地应用这些答案。在我看来,一个不回答问题且阻止回答的回答应该是一个评论。 - undefined
楼主实际的问题是“我可以省略async/await吗?”,这个问题在我的回答末尾得到了回答,在指出了(许多)忽视错误的陷阱之后。 - undefined

2
这取决于你所说的“proper”的含义:)
例如:你是否对在“fire and forget”调用中抛出的异常感兴趣?如果不是,那么这样做还算可以。但你需要考虑任务存在的环境。
例如,如果这是一个asp.net应用程序,并且您在由调用.aspx或.svc引起的线程生命周期内执行此操作。该任务成为该(前台)线程的后台线程。在完成“fire and forget”任务之前,应用程序池可能会清理前台线程。
因此,还要考虑您的任务位于哪个线程中。
我认为本文为您提供了一些有用的信息: https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx 还请注意,如果您的任务中没有返回值,则任务将不返回异常信息。这是Microsoft考试70-483的参考书的来源。 可能有免费版本在线上;P https://www.amazon.com/Exam-Ref-70-483-Programming-C/dp/0735676828 也许需要知道的是,如果异步方法由非异步方法调用并且您希望知道其结果。您可以使用.GetAwaiter().GetResult()。
此外,我认为重要的是要注意异步和多线程之间的区别。
只有在存在使用计算机其他部分而不是CPU的操作时,异步才有用。因此,诸如网络或I/O操作之类的事物。然后使用异步告诉系统继续在其他地方使用CPU功率,而不是“阻止”该线程在CPU上等待响应。
多线程是在CPU的不同线程上分配操作(例如,创建一个任务,该任务创建前台线程的后台线程…前台线程是构成应用程序的线程,它们是主要的,后台线程与前台线程相关联。如果关闭链接的前台线程,则后台线程也会关闭) 这允许CPU同时处理不同的任务。
将这两者结合起来可以确保CPU不会在只有4个线程的情况下被阻塞。但是,在等待等待I/O操作的异步任务时,可以打开更多的线程。
希望这些信息能够帮助你完成你所做的任何事情 :)

谢谢你的见解!是的,我对任何异常都不感兴趣(在DoWorkAsync中有异常处理)。而且是的,所有这些都是在一个ASP.NET应用程序内。这是否改变了你的答案? - silent
1
不用真的,你可以跳过关于返回值的部分。那个不是必要的 :) 但我确实想强调一下关于后台线程和前台线程的部分。我们公司决定在aspx页面的线程生命周期内使用“发射并忘记”的方法来额外调用Azure应用洞察。结果我们很惊讶地发现没有多少日志被记录下来。这是因为任务在页面操作结束之前刚刚被实例化,这意味着线程和刚创建的任务都被应用程序池清理掉了。所以请看一下我在链接中提到的内容。 - Davey van Tilburg
对于 ASP.NET 部分,我将采用 Scott 的文章中的 queuebackgroundworkitem。谢谢。 - silent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接