【问题标题】:Exception-Handling asynchronous programming异常处理异步编程
【发布时间】:2018-03-01 08:30:23
【问题描述】:

我这里有一些代码。这是一个真实类的简化版本:

public class Delayer
{
    //it has to be unawaitable
    public async void Execute(Action action)
    {
        await Task.Delay(10).ConfigureAwait(false);
        action.BeginInvoke(null, null); //action.Invoke();
    }
}

我用它:

private static Task TestFoo()
{
    throw new Exception();
}


delayer.Execute(async () =>
{
    //do something else
    await TestFoo().ConfigureAwait(false);
});

我不能通过将Execute 方法传递给try/catch 来处理这个异常,我也不能通过将action.BeginInvoke(null, null) 传递给try/catch 来处理这个异常。如果只有在将异步 lambda 传递给 Execute 方法时将它包围起来,我就可以处理它。

我的问题是:为什么要使用 await 执行异步 lambda?因为如果不使用 await 执行,异常就会被吞掉。

我希望Execute 方法能够吞下操作引发的所有异常。任何想法如何做到这一点?我做错了什么?

加法:

Execute 的行为一定是“开火即忘操作”。

【问题讨论】:

  • 只在执行方法中实现try catch并处理那里的所有异常。

标签: c# .net asynchronous exception-handling async-await


【解决方案1】:

编辑

如果你真的非常想要一个 Fire and Forget 方法,唯一要做的就是 在 Execute 方法中捕获所有异常。但是,如果您希望能够捕获异常而不是在不可等待的 Action 上使用 BeginInvoke,则必须接受可等待的任务。

public class Delayer
{
    public async Task Execute(Func<Task> action) // or public async void Execute(Func<Task> action) if you insist on it.
    {
        try
        {
            await Task.Delay(10).ConfigureAwait(false);
            await action.Invoke();
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

然后你就可以放心了

void CallDelayedMethod()
{
    var delayer = new Delayer();

    delayer.Execute(ThrowException);
}

public Task ThrowException()
{
    throw new Exception();  
}

我仍然会返回一个任务并将其留给调用者忽略它,不等待它(触发并忘记)。

原答案

Delayer 类中使用异步 void 签名没有遵循最佳实践。

public async void Execute(Action action)

应该是

public async Task Execute(Action action)

所以你可以等待Execute 的电话。否则,这只是一个“一劳永逸”的操作,这使得捕捉异常变得困难。通过让它等待你可以这样做:

try
{
    await Delayer.Execute(...);
}
catch(Exception ex)
{
    ....
}

来自the best practices

Async void 方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,会捕获该异常并将其放置在任务对象上。对于 async void 方法,没有 Task 对象,因此从 async void 方法抛出的任何异常都将直接在 async void 方法启动时处于活动状态的 SynchronizationContext 上引发。

此外,如果您想将可等待的操作传递给任务,您应该让 Execute 接受任务:

public async Task Execute(Func<Task> action)
{
    await Task.Delay(10).ConfigureAwait(false);
    await action.Invoke();
}

【讨论】:

  • 这个类的主要思想是“只是一个火而忘记的操作”。所以Execute 必须是无法等待的。
  • @DdarkSideE 啊,你没有在问题中提到这一点。这也是一个很常见的错误。但我编辑了我的答案
  • 是否可以让Execute 同时采用 Action 和 Func 而不重载 Execute 并且不用 Func 包装 Action ?
  • 嗯,我不这么认为。但是重载并没有那么糟糕,或者你不能修改那个类吗?
猜你喜欢
  • 2019-08-12
  • 2013-09-10
  • 1970-01-01
  • 2019-08-29
  • 2022-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-07
相关资源
最近更新 更多