如何在.NET中调用异步函数而不等待它[重复]
Posted
技术标签:
【中文标题】如何在.NET中调用异步函数而不等待它[重复]【英文标题】:How to call an async function in .NET without waiting for it [duplicate] 【发布时间】:2019-08-05 15:41:18 【问题描述】:我在 C# 中有一个异步函数如下:
private static async Task AddEmoji(string emoji)
...
我有一种情况,我想调用它,但我不想等待它的结果,我不在乎它是否有效。解决这个问题的正确方法是什么?我以为我可以在不添加异步参数的情况下调用它:
AddEmoji("????");
... 可行,但 VS 给了我一个 CS4014 编译警告,所以我想这是不对的。然后我尝试为它创建一个任务:
Task.Run(() => await AddEmoji("????"));
但这给了我一个实际的编译错误:
错误 CS4034 'await' 运算符只能在异步中使用 拉姆达表达式。考虑用 'async' 修饰符。
解决这个问题的正确方法是什么?
【问题讨论】:
你试过不等待就调用它吗:Task.Run(() => AddEmoji("????")); 是第二次尝试 - 你可能需要Task.Run(async () => await AddEmoji("????"));
- 但是,你可能只是可以将任务放在地板上...... GC.KeepAlive(somethingYouDoNotCareAbout)
在大多数情况下效果很好(调用 @ 987654326@ 实际上只是一个不透明的方法,什么都不做)或者只是抑制警告!
如果您不在乎它是否有效,为什么还要首先调用它?
大家好 - 如果我调用 @NikolaBabic 或 Marc 的建议,我会收到 CS4014(考虑应用“等待”运算符)警告。并不是说我完全不在乎它是否有效——我只是不想等待它返回或知道它是否有效,因为如果它失败了,我不会特别做不同的事情。跨度>
请注意,在 .NET 4.5 之前将任务放在地板上是一个非常糟糕的主意。如果任务包含异常,任务的终结器将重新抛出它。这在 4.5 中已更改。
【参考方案1】:
在您的 Task.Run() 中,等待不会进入 Func。您通常会等待任务本身(将await
放在Task.Run()
之前),但我认为在您的情况下,您可以省略等待以便不等待并忽略结果:
Task.Run(() => AddEmjoi("?"))
【讨论】:
这抓取了一个新的 ThreadPool 线程只是为了运行AddEmoji
的第一位,这是不必要的昂贵
如果我这样做,我会得到一个 CS4014(考虑应用“await”运算符)。它有效,但显然这不是最佳实践,因为我收到了警告。所以我想知道“正确”的做法是什么。我也同意@canton7 - 似乎在这里使用 Task.Run 只是创建了一个我没有使用的额外层。【参考方案2】:
我不想等待它的结果,我不在乎它是否有效。
这是非常不寻常的;首先要检查的是这个假设,您实际上不需要知道它何时完成(或 if 它完成),并且您可以默默地忽略所有异常。这也意味着当您的应用程序退出时,您可以接受这项工作永远不会完成(同样,您不会知道,因为异常被忽略) - 请记住,在 ASP.NET 等托管场景中,您 不要控制您的应用何时退出。
也就是说,你可以这样“一劳永逸”:
var _ = AddEmoji("?");
【讨论】:
我经常遇到这种情况。考虑 1) 一种从流中异步读取数据的方法,在无限循环中,发布它读取的帧。此方法永远不会返回,但我希望我的应用程序在抛出意外异常时崩溃; 2)一个System.Threading.Timer
回调,做异步操作,有自己的错误处理; 3) 状态机的状态入口处理程序,它执行一些异步工作,并在工作成功或失败时在状态机上触发适当的事件。对于这些情况,我还没有找到比 async void
更好的方法。
@canton7:第一个例子是不是“一劳永逸”,因为你想要检测错误。第二个示例是 async void
的一个很好的用例 - 请注意,它是一个 事件,这正是 async void
首先被添加到语言中的原因。第三个例子更可疑。在我看来,一个更好的设计可能是可能的,但即使在那个例子中,你在谈论一个“处理程序”——即一个逻辑事件,即使它不是一个字面事件,所以async void
在那里是可以接受的,也是。
感谢您的回复。在“永远不应该使用async void
”之后,很难找到解决这些情况的好方法。在第一个示例中,包含无限循环的方法通常有自己的错误处理,但未处理的异常应该会导致应用程序崩溃(例如git.io/fje5z)。在那种情况下,async void
也很合适,对吧?由于在捕获的 SynchronizationContext / ThreadPool 上重新抛出了未处理的异常,因此具有预期的效果。同意2的解释,还在寻找3上更好的设计!
@canton7:我是“避免async void
”咒语的鼻祖。正如我的original article on the subject 中所述,指导方针是避免async void
,除非您将它用于事件处理程序(或类似事件的语义)。所以我并不是说它应该永远被使用——对于async
新手来说,当他们打算使用async Task
时使用async void
只是一个常见的错误。【参考方案3】:
这不适用于 asp.net -- async void
方法不允许在 asp.net 应用程序中使用。不过,控制台、WPF、Winforms 等应用程序都很好。请参阅 cmets 了解完整讨论。
最好的办法是在 async void
方法中 await
它。
只写AddEmoji("?");
的问题是:如果它失败了会发生什么?如果在AddEmoji
中引发异常,则该异常将捆绑在Task
中,AddEmoji
返回。但是,因为您将 Task
扔掉了,所以您永远不会知道。所以AddEmoji
可能会以一种糟糕的方式失败,但你永远不会知道。这就是编译器给你一个错误的原因。
但是,async void
方法可以避免这种情况。如果在 async void
方法中引发异常,则该异常会在 ThreadPool 上重新引发,这将导致您的应用程序崩溃。你肯定知道出了什么问题!
人们建议不要使用async void
方法,但它们非常适合即发即弃的情况。该建议源于人们在确实想知道操作何时完成时尝试使用async void
方法。
public async void AddEmojiImpl(string emoji) => await AddEmoji(emoji);
或者
public async void AddEmojiImpl(string emoji)
try
await AddEmojiImpl(emoji);
catch (EmojiAddException e)
// ...
本地函数对此特别有用。
【讨论】:
“但它们非常适合即发即弃的情况。” - 不,我强烈反对 - 这是一个非常糟糕的事情,原因 是:有些框架(包括 MVC)主动阻止 - 意思是:它会导致异常(由于同步上下文选择不允许它)。我在语义上同意你的观点,但实际上:养成做事的习惯真的很糟糕。如果它今天不咬你,它可能会在以后由于内部框架的变化而意外地开始失败。由于正是这样做,我不得不重新发行库。 @MarcGravell 请详细说明您所说的“主动阻止”是什么意思?您的意思是捕获的 SynchronizationContext 不允许将继续发布到它吗?这不是我遇到过的——你有这方面的资料吗?当然,在您在 cmets 中提倡的“只需调用它并使用GC.KeepAlive
来抑制警告”方法中存在完全相同的风险,因为 AddEmoji
可以随时回发到 SynchronizationContext?
通过“主动阻止”,我的意思是:当你这样做时,它实际上会抛出异常;同步上下文执行此操作 - ASP.NET 的错误是 i.stack.imgur.com/uad2C.png - 这仅在 调用 async void
方法时发生 - 引发 from here, specifically(当 _state.AllowVoidAsyncOperations
是false
)。这样做?
当您使用Task
调用GC.KeepAlive
时(将其放在地板上同时保持编译器满意),它不会触发同步上下文为的async void
场景明确寻找(阻止)-因为它现在是async Task
@MarcGravell 有趣。完美,这正是我所追求的。谢谢!这是我看到的反对async void
的第一个真正可靠的论点。我会更新我的答案。以上是关于如何在.NET中调用异步函数而不等待它[重复]的主要内容,如果未能解决你的问题,请参考以下文章