async/await - 何时返回任务与无效?

async/await - when to return a Task vs void?

提问人:user981225 提问时间:8/27/2012 最后编辑:Boiethiosuser981225 更新时间:1/12/2023 访问量:561779

问:

在什么情况下要使用

public async Task AsyncMethod(int num)

而不是

public async void AsyncMethod(int num)

我能想到的唯一情况是,如果您需要任务能够跟踪其进度。

此外,在以下方法中,async 和 await 关键字是否不需要?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}
C# 异步 NET-4.5

评论

47赞 Ben 6/16/2016
@Fred 大多数情况下,但并非总是如此。这只是约定,此约定的可接受例外情况是基于事件的类或接口协定,请参阅 MSDN。例如,不应重命名常见的事件处理程序,例如 Button1_Click。
18赞 Bob Vale 4/7/2017
只是一个你不应该在任务中使用的注释,你应该改用Thread.Sleepawait Task.Delay(num)
78赞 undefined 5/1/2017
@fred我不同意这一点,但 IMO 添加异步后缀仅在您提供具有同步和异步选项的接口时才应使用。当只有一个意图时,Smurf 用异步命名事物是没有意义的。举个例子,并不是因为任务上的所有方法都是异步的Task.DelayTask.AsyncDelay
18赞 boggy 5/27/2017
今天早上我遇到了一个有趣的问题,即 webapi 2 控制器方法,它被声明为 .该方法崩溃,因为它使用的是声明为控制器成员的实体框架上下文对象,该对象在方法完成执行之前被释放。框架在其方法完成执行之前释放了控制器。我将方法更改为异步任务,它起作用了。async voidasync Task
2赞 Milad 7/3/2017
我认为这个链接会有所帮助。

答:

563赞 user743382 8/27/2012 #1
  1. 通常,您需要返回一个 .主要的例外应该是需要返回类型(对于事件)。如果没有理由禁止调用者执行您的任务,为什么要禁止它呢?Taskvoidawait

  2. async返回的方法在另一个方面很特殊:它们表示顶级异步操作,并且具有在任务返回异常时起作用的其他规则。最简单的方法是通过一个示例来显示差异:void

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

f的例外总是“观察到”。留下顶级异步方法的异常被简单地视为任何其他未经处理的异常。的异常从未被观察到。当垃圾回收器来清理任务时,它看到该任务导致了异常,并且没有人处理该异常。发生这种情况时,处理程序将运行。你永远不应该让这种情况发生。举个例子,gTaskScheduler.UnobservedTaskException

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

是的,使用,在这里,他们确保在抛出异常时您的方法仍然正常工作。asyncawait

有关详细信息,请参阅:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

评论

12赞 Stephen Cleary 8/28/2012
我的意思是而不是在我的评论中。来自 f 的异常将传递给 . 将引发 ,但如果未处理,则不再使进程崩溃。在某些情况下,忽略这样的“异步异常”是可以接受的。fgSynchronizationContextgUnobservedTaskExceptionUTE
3赞 Stephen Cleary 8/28/2012
如果您有多个 s 导致异常。你通常只需要处理第一个,而你经常想忽略其他的。WhenAnyTask
1赞 8/28/2012
@StephenCleary 谢谢,我想这是一个很好的例子,尽管这取决于你首先调用的原因是否可以忽略其他异常:我的主要用例仍然最终等待剩余的任务完成,无论是否有例外。WhenAny
21赞 user981225 8/29/2012
我有点困惑为什么你建议返回一个任务而不是void。就像你说的,f() 会抛出 和 exception,但 g() 不会。了解这些后台线程异常不是最好的吗?
2赞 8/29/2012
@user981225 确实如此,但这就成了 g 调用者的责任:每个调用 g 的方法都应该是异步的,并且也使用 await。这是一个指导方针,而不是一个硬性规则,你可以决定在你的特定程序中,让 g return void 更容易。
62赞 Davide Icardi 4/11/2014 #2

我遇到了这篇非常有用的文章,内容是关于杰罗姆·拉班(Jérôme Laban)撰写的:https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.htmlasyncvoid

底线是 可能会使系统崩溃,通常只应在 UI 端事件处理程序上使用。async+void

这背后的原因是 AsyncVoidMethodBuilder,在此示例中为 none。当没有 环境同步上下文,任何未由 异步 void 方法的主体在 ThreadPool 上重新抛出。而 似乎没有其他合乎逻辑的地方可以处理这种未经处理的地方 可能会抛出异常,不幸的影响是该过程 正在终止,因为 ThreadPool 上出现未经处理的异常 自 .NET 2.0 以来有效地终止进程。您可以拦截 使用 AppDomain.UnhandledException 事件的所有未处理的异常, 但是无法从此事件中恢复该过程。

在编写 UI 事件处理程序时,异步 void 方法在某种程度上是 无痛,因为异常的处理方式与 非异步方法;它们被抛出到 Dispatcher 上。有一个 从此类异常中恢复的可能性,非常正确 在大多数情况下。但是,在 UI 事件处理程序之外,async void 方法使用起来很危险,而且可能不那么容易找到。

评论

0赞 N. M. 8/31/2019
这是对的吗?我以为异步方法中的异常是在 Task 中捕获的。而且 afaik 总是有一个 deafault SynchronizationContext
14赞 bboyle1234 2/16/2018 #3

我认为您也可以使用它来启动后台操作,只要您小心捕获异常即可。思潮?async void

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

评论

4赞 user8128167 6/19/2018
调用 async void 的问题在于,您甚至无法取回任务,您无法知道函数的任务何时完成
2赞 Florian Winter 8/27/2018
这对于(罕见的)后台操作很有意义,在这些操作中,您不关心结果,并且不希望异常影响其他操作。典型的用例是将日志事件发送到日志服务器。您希望这在后台发生,并且不希望您的服务/请求处理程序在日志服务器关闭时失败。当然,您必须捕获所有异常,否则您的进程将被终止。或者有更好的方法来实现这一目标?
1赞 Si Zi 10/16/2018
异步 void 方法的异常不能使用 catch 捕获。msdn.microsoft.com/en-us/magazine/jj991977.aspx
7赞 bboyle1234 10/17/2018
@SiZi捕获在异步 void 方法内部,如示例所示,则捕获。
1赞 Milney 1/17/2020
当你的需求更改为要求不做任何工作时,如果你不能记录它,那么你必须改变整个 API 中的签名,而不仅仅是改变当前有 // 忽略异常的逻辑怎么办
63赞 user8128167 6/19/2018 #4

调用 async void 的问题在于

你甚至拿不回任务。您无法知道函数的任务何时完成。—— 异步和等待中的速成班 |旧事物 新事物

以下是调用异步函数的三种方法:

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

在所有情况下,函数都会转换为任务链。区别在于函数返回的内容。

在第一种情况下,该函数返回一个最终生成 t 的任务。

在第二种情况下,该函数返回一个没有产品的任务,但您可以 仍然在等待它,知道它何时完成。

第三种情况是令人讨厌的情况。第三种情况与第二种情况类似,只是 你甚至没有把任务拿回来。你无法知道什么时候 函数的任务已完成。

异步 void 情况是“火灾和 forget“:你启动了任务链,但你不关心它什么时候启动 完成。当函数返回时,您所知道的就是一切 直到第一个等待已执行。第一次之后的一切都在等待 将在将来某个未指定的时间点运行,而您没有 访问。

-6赞 Serg Shevchenko 9/21/2018 #5

我的答案很简单 你不能等待 void 方法

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

所以如果方法是,最好是等待,因为你可能会失去优势。asyncasync

评论

0赞 shanavascet 7/16/2022
用于事件处理程序的 void async 方法。因此,有必要在不想等待完成的地方使用异步。
0赞 zeroG 11/8/2022
@shanavascet那么Beetwen经典的虚空方法和虚空任务有什么区别呢?
3赞 Amit Bisht 1/12/2023 #6

简单说明:
await 可用于等待执行完成,其类型
await 可用于等待执行完成,但不返回任何数据 can't be awawait返回
任何数据 [示例:异步事件执行]
async Task<T> method()return valueTasync Task method()async void method()