异步/等待-什么时候返回Task vs void?

本文翻译自:async/await - when to return a Task vs void?

Under what scenarios would one want to use 在什么情况下您想使用

public async Task AsyncMethod(int num)

instead of 代替

public async void AsyncMethod(int num)

The only scenario that I can think of is if you need the task to be able to track its progress. 我能想到的唯一情况是您是否需要任务以跟踪其进度。

Additionally, in the following method, are the async and await keywords unnecessary? 另外,在以下方法中,async和await关键字是否不必要?

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

#1楼

参考:https://stackoom.com/question/oxED/异步-等待-什么时候返回Task-vs-void


#2楼

1) Normally, you would want to return a Task . 1)通常,您需要返回Task The main exception should be when you need to have a void return type (for events). 主要的例外应该是当您需要具有void返回类型(用于事件)时。 If there's no reason to disallow having the caller await your task, why disallow it? 如果没有理由不允许呼叫者await您的任务,为什么要拒​​绝它呢?

2) async methods that return void are special in another aspect: they represent top-level async operations , and have additional rules that come into play when your task returns an exception. 2)返回void async方法在另一方面是特殊的:它们代表顶级异步操作 ,并且在您的任务返回异常时会发挥作用。 The easiest way is to show the difference is with an example: 最简单的方法是通过一个示例来显示差异:

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 's exception is always "observed". f的例外始终是“观察到”。 An exception that leaves a top-level asynchronous method is simply treated like any other unhandled exception. 留下顶级异步方法的异常就像其他未处理的异常一样被对待。 g 's exception is never observed. g的异常从未观察到。 When the garbage collector comes to clean up the task, it sees that the task resulted in an exception, and nobody handled the exception. 当垃圾收集器清理任务时,它会看到任务导致异常,并且没有人处理该异常。 When that happens, the TaskScheduler.UnobservedTaskException handler runs. 发生这种情况时,将运行TaskScheduler.UnobservedTaskException处理程序。 You should never let this happen. 您绝对不要让这种情况发生。 To use your example, 以您的示例为例,

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

Yes, use async and await here, they make sure your method still works correctly if an exception is thrown. 是的, await这里使用asyncawait ,它们可以确保在引发异常时您的方法仍然可以正常工作。

for more information see: http://msdn.microsoft.com/en-us/magazine/jj991977.aspx 有关更多信息,请参见: http : //msdn.microsoft.com/zh-cn/magazine/jj991977.aspx


#3楼

I have come across this very usefull article about async and void written by Jérôme Laban: https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html 我看过JérômeLaban撰写的关于asyncvoid非常有用的文章: https : //jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async -void.html

The bottom line is that an async+void can crash the system and usually should be used only on the UI side event handlers. 底线是async+void可能会使系统崩溃,通常只应在UI端事件处理程序上使用。

The reason behind this is the Synchronization Context used by the AsyncVoidMethodBuilder, being none in this example. 其背后的原因是AsyncVoidMethodBuilder使用的“同步上下文”,在此示例中没有。 When there is no ambient Synchronization Context, any exception that is unhandled by the body of an async void method is rethrown on the ThreadPool. 当没有环境同步上下文时,异步void方法的主体无法处理的任何异常都会在ThreadPool上重新抛出。 While there is seemingly no other logical place where that kind of unhandled exception could be thrown, the unfortunate effect is that the process is being terminated, because unhandled exceptions on the ThreadPool effectively terminate the process since .NET 2.0. 尽管似乎没有其他逻辑地方可以抛出这种未处理的异常,但是不幸的是,该进程正在终止,因为自.NET 2.0起,ThreadPool上的未处理的异常有效地终止了该进程。 You may intercept all unhandled exception using the AppDomain.UnhandledException event, but there is no way to recover the process from this event. 您可以使用AppDomain.UnhandledException事件来拦截所有未处理的异常,但是无法从此事件中恢复进程。

When writing UI event handlers, async void methods are somehow painless because exceptions are treated the same way found in non-async methods; 在编写UI事件处理程序时,异步void方法可以轻松实现,因为对异常的处理方式与非异步方法相同。 they are thrown on the Dispatcher. 他们扔在调度程序上。 There is a possibility to recover from such exceptions, with is more than correct for most cases. 有可能从此类异常中恢复,对于大多数情况而言,这种恢复是正确的。 Outside of UI event handlers however, async void methods are somehow dangerous to use and may not that easy to find. 但是,在UI事件处理程序之外,异步void方法在某种程度上使用起来很危险,而且可能不那么容易找到。


#4楼

I got clear idea from this statements. 我从这句话中得到了清晰的主意。

  1. Async void methods have different error-handling semantics. 异步void方法具有不同的错误处理语义。 When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. 从异步Task或异步Task方法抛出异常时,将捕获该异常并将其放置在Task对象上。 With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext(SynchronizationContext represents a location "where" code might be executed. ) that was active when the async void method started 使用异步void方法时,没有Task对象,因此从异步void方法抛出的任何异常都将直接在SynchronizationContext(SynchronizationContext表示可能在其中执行代码的位置)上引发。开始了

Exceptions from an Async Void Method Can't Be Caught with Catch 异步无效方法的异常无法捕获

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

These exceptions can be observed using AppDomain.UnhandledException or a similar catch-all event for GUI/ASP.NET applications, but using those events for regular exception handling is a recipe for unmaintainability(it crashes the application). 可以使用AppDomain.UnhandledException或类似的GUI / ASP.NET应用程序的全部捕获事件来观察这些异常,但是使用这些事件进行常规异常处理是导致无法维护的秘诀(会使应用程序崩溃)。

  1. Async void methods have different composing semantics. 异步void方法具有不同的构成语义。 Async methods returning Task or Task can be easily composed using await, Task.WhenAny, Task.WhenAll and so on. 返回任务或任务的异步方法可以使用await,Task.WhenAny,Task.WhenAll等轻松组成。 Async methods returning void don't provide an easy way to notify the calling code that they've completed. 返回void的异步方法不能提供一种简单的方法来通知调用代码它们已完成。 It's easy to start several async void methods, but it's not easy to determine when they've finished. 启动多个异步void方法很容易,但是确定它们何时完成并不容易。 Async void methods will notify their SynchronizationContext when they start and finish, but a custom SynchronizationContext is a complex solution for regular application code. 异步void方法在启动和完成时会通知其SynchronizationContext,但是自定义SynchronizationContext是常规应用程序代码的复杂解决方案。

  2. Async Void method useful when using synchronous event handler because they raise their exceptions directly on the SynchronizationContext, which is similar to how synchronous event handlers behave 使用同步事件处理程序时,Async Void方法很有用,因为它们直接在SynchronizationContext上引发异常,这与同步事件处理程序的行为类似

For more details check this link https://msdn.microsoft.com/en-us/magazine/jj991977.aspx 有关更多详细信息,请检查此链接https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx


#5楼

I think you can use async void for kicking off background operations as well, so long as you're careful to catch exceptions. 我认为您也可以使用async void来启动后台操作,只要您小心捕获异常即可。 Thoughts? 有什么想法吗?

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

#6楼

The problem with calling async void is that you don't even get the task back, you have no way of knowing when the function's task has completed (see https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/?p=96655 ) 调用异步void的问题是您甚至没有将任务取回,也无法知道函数的任务何时完成(请参阅https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/ ?p = 96655

Here are the three ways to call an async function: 这是调用异步函数的三种方法:

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

In all the cases, the function is transformed into a chain of tasks. 在所有情况下,功能都会转换为一系列任务。 The difference is what the function returns. 区别在于函数返回什么。

In the first case, the function returns a task that eventually produces the t. 在第一种情况下,该函数返回一个最终产生t的任务。

In the second case, the function returns a task which has no product, but you can still await on it to know when it has run to completion. 在第二种情况下,该函数返回没有产品的任务,但是您仍然可以等待它知道何时运行完成。

The third case is the nasty one. 第三种情况是令人讨厌的。 The third case is like the second case, except that you don't even get the task back. 第三种情况与第二种情况类似,不同之处在于您甚至没有将任务取回。 You have no way of knowing when the function's task has completed. 您无法知道函数任务何时完成。

The async void case is a "fire and forget": You start the task chain, but you don't care about when it's finished. 异步无效情况是一种“即发即弃”:您启动了任务链,但并不关心何时完成。 When the function returns, all you know is that everything up to the first await has executed. 函数返回时,您所知道的就是直到第一次等待的所有操作都已执行。 Everything after the first await will run at some unspecified point in the future that you have no access to. 第一次等待后的所有内容将在将来无法访问的某个未指定的时间运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值