删除async void

18 篇文章 0 订阅

我再说一遍:async await很棒。与每个伟大的工具一样,我们有责任了解如何最好地使用它。

异步传播永久链接

让我们举一个 Xamarin 应用程序的示例,用户通过按下应用程序中的按钮来订购咖啡。

我们显然有一个班级负责准备咖啡:

public class CoffeeService
{
    public async Task PrepareCoffeeAsync()
    {
        // Asynchronously prepare an awesome coffee
        await Task.Delay(2000);
    }
}

由于准备咖啡需要时间,而且我们现在不知道需要多长时间(因为可能发生的事件、罢工或需要找到一袋新的咖啡粒)这个方法被标记为异步并返回一个任务。

任何调用的代码PrepareCoffeeAsync都需要等待它的完成。

最明显的方法是将调用者标记为异步,如下面的视图模型:

public class CoffeeViewModel
{
    public async Task PrepareCoffeeAsync()
    {
        IsBusy = true;
        var coffeeService = new CoffeeService();
        await coffeeService.PrepareCoffeeAsync();
        IsBusy = false;
    }
}

重复相同的过程,您将达到无法将返回类型更改为 Task 的地步,您将面临异步 void。

async void 有那么糟糕吗?永久链接

我可以这样总结:

  • 它生成编译器警告
  • 如果那里没有捕获异常,则您的应用程序已死
  • 您可能没有合适的调用堆栈来调试
  • 如果您的应用程序崩溃:
    • 你的用户不会高兴
    • 你的老板不会高兴的
    • 你会失去你的工作,然后你的另一半……

实际上我不知道最后一个,但无论如何, async void 是一个坏人

有很多很棒的文章,比如Stephen ClearyPhil Haack的文章(下面的链接),它们详细解释了 async void 的影响,我非常鼓励你阅读它们。

但我别无选择!永久链接

不幸的是,这是真的。在异步传播的某一时刻,您将到达一种无法更改返回类型的方法,例如:

  • 生命周期方法
  • 事件处理程序
  • 代表
  • Lambda 表达式

我们最终得到这样的代码:

public async void OnPrepareButtonClick(object sender, EventArgs e)
{
    Button button = (Button)sender;

    button.IsEnabled = false;
    activityIndicator.IsRunning = true;

    var coffeeService = new CoffeeService();
    await coffeeService.PrepareCoffeeAsync();

    activityIndicator.IsRunning = false;
    button.IsEnabled = true;
}

删除异步无效永久链接

如果您只是对在项目中使用的可重用代码感兴趣,请查看受本文内容启发的AsyncAwaitBestPratices库。

研究代码永久链接

通过一些重构,我们可以在代码中隔离异步 void方法。

但首先让我们研究和注释代码中的内容:

  1. 铸造成一个按钮
    Button button = (Button)sender;
  2. 向用户展示我们正在启动一个异步操作
    button.IsEnabled = false;
    activityIndicator.IsRunning = true;
  3. 运行异步代码
    var coffeeService = new CoffeeService();
    await coffeeService.PrepareCoffeeAsync();
  4. 向用户展示我们完成了
    activityIndicator.IsRunning = false;
    button.IsEnabled = true;

搬东西永久链接

在这种情况下,我们可以将步骤 2 到 4 移到异步方法中。

public async void OnPrepareButtonClick(object sender, EventArgs e)
{
    Button button = (Button)sender;
    await PrepareCoffeeAsync(button);
}

public async Task PrepareCoffeeAsync(Button button)
{
    button.IsEnabled = false;
    activityIndicator.IsRunning = true;

    var coffeeService = new CoffeeService();
    await coffeeService.PrepareCoffeeAsync();

    activityIndicator.IsRunning = false;
    button.IsEnabled = true;
}

现在,我们的事件处理程序在方法结束时只有一个 await 。这就是我们需要继续做的事情,并使我们的代码更安全。

删除异步无效永久链接

对于事件处理程序,等待PrepareCoffeeAsync现在是无用的。由于后面没有代码,所以不需要完成信息或结果。

它现在是典型的“一劳永逸”方法。

因此,我们可以删除异步:

public void OnPrepareButtonClick(object sender, EventArgs e)
{
    Button button = (Button)sender;
    PrepareCoffeeAsync(button);
}

我们不再有async void但我们还没有完成,因为没有处理任何异常

处理异常永久链接

使用 try catch 块永久链接

使用 try catch 块来处理异常当然是可能的:

public async Task PrepareCoffeeAsync(Button button)
{
    try
    {
        button.IsEnabled = false;
        activityIndicator.IsRunning = true;
    
        var coffeeService = new CoffeeService();
        await coffeeService.PrepareCoffeeAsync();
    
        activityIndicator.IsRunning = false;
        button.IsEnabled = true;
    }
    catch (Exception ex)
    {
        // Do something
        System.Diagnostics.Debug.WriteLine(ex);
    }
}

这种方法的问题是它会产生大量的代码重复,因为在典型的代码库中有很多地方存在async void 。

带扩展永久链接

我们现在需要的是一些任务的扩展方法来处理异常,这些异常可以替换整个代码中的所有这些 try catch 块。

有很多方法可以做到这一点,没有一种方法真的比其他方法更好。这总是一个品味问题。

介绍 FireAndForgetSafeAsync 和 IErrorHandler永久链接

所以让我介绍一下我最喜欢的扩展方法:

public static class TaskUtilities
{
#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
    public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            handler?.HandleError(ex);
        }
    }
}

FireAndForgetSafeAsync方法基本上将任务包装到 try catch 块中。如果发生错误,它将异常发送到错误处理程序,并应实现以下接口:

public interface IErrorHandler
{
    void HandleError(Exception ex);
}

当然,将错误处理程序设置为null不是什么事情!

我通常喜欢我的视图模型:

  • IErrorHandler在运行时对注入有一个引用
  • 直接实现接口

完成代码永久链接

现在我们已经有了前两种方法,我们可以使我们的代码更安全:

public void OnPrepareButtonClick(object sender, EventArgs e)
{
    IErrorHandler errorHandler = null; // Get an instance from somewhere
    Button button = (Button)sender;
    PrepareCoffeeAsync(button).FireAndForgetSafeAsync(errorHandler);
}

结论永久链接

通过非常复杂的代码库应用完全相同的步骤,使我和我的团队能够克服Xamarin 应用程序中的许多问题,其中异步等待的使用非常广泛。

一点一点地,四处移动和使用FireAndForgetSafeAsync 减少了崩溃的数量,提高了我们的错误报告准确性。

最后,重要的是要记住,无论你使用什么手段,

只需删除 async void !

一如既往,请随时阅读我以前的帖子并在下面发表评论,我将非常乐意回答。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值