使用async/await的最佳实践

本文关键字:最佳 await async 使用 | 更新日期: 2023-09-27 18:19:41

假设我有以下类定义:

public class Calculator
{
    public CalculatorResult Calculate()
    {
        return LongRunningCalculation();
    }
    private CalculatorResult LongRunningCalculation()
    {
        return new CalculatorResult(0.00);
    }
}
public class ClassThatUsesACalculator
{
    private readonly Calculator calculator;
    public ClassThatUsesACalculator()
    {
        this.calculator = new Calculator();
    }
    public void DoWork()
    {
        for (int i = 0; i < 10; i++)
        {
            var result = calculator.Calculate();
            DoSomethingWithCalculationResult(result);
            DoLightWork();
            OnProgressChanged();
        }
    }
}
public partial class Form : Form
{
    public Form()
    {
        InitializeComponent();
    }
    private void Method(object sender, EventArgs e)
    {
        DoWork();
    }
    private void DoWork()
    {
        var calculator = new ClassThatUsesACalculator();
        calculator.ProgressChanged += (s, e) =>
        {
            // Update progressbar
        };
        calculator.DoWork();
    }
}

如果我想在表单上异步地完成DoWork()中完成的工作,我可以添加一个使用Task.Run()返回任务的方法(GetCalculationTask),并添加一个异步事件处理程序,即For a button(MethodOne)。

如果我错了,请纠正我,但在我看来,当ClassThatUsesACalculatorCalculator类位于我不拥有的库中时,这将是唯一的选项。

private Task GetCalculationTask(IProgress<CalculatorProgress> progress)
{
    var calculator = new ClassThatUsesACalculator();
    calculator.ProgressChanged += (s, e) =>
    {
        progress.Report(new CalculatorProgress(0));
    };
    return Task.Run(() =>
    {
        calculator.DoWork();
    });
}
private async void MethodOne(object sender, EventArgs e)
{
    IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress>   (UpdateProgressBar);
    await GetCalculationTask(progress);
}

如果我确实拥有这个图书馆,我想还有两个选择,其中一个非常像第一个。可能是因为我自己缺乏理解。

ClassThatUsesACalculator上创建一个封装DoWork()方法的方法,然后从窗体上的异步方法调用该方法。

或者,

  1. 使用Task.Run()LongRunningCalculation()封装在Calculator类上。

    public Task<CalculatorResult> CalculateAsync()
    {
        return Task.Run(() =>
        {
            return LongRunningCalculation();
        });
    }
    
  2. ClassThatUsesACalculator上创建一个异步方法——等待新创建的方法的调用。

    public async Task DoWorkAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            var result = await calculator.CalculateAsync();
            DoSomethingWithCalculationResult(result);
            DoLightWork();
            OnProgressChanged(); 
        }
    }
    
  3. 在表单(MethodThree)上创建异步方法

    private async void MethodThree(object sender, EventArgs e)
    {
        IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress>(UpdateProgressBar);
        var calculator = new ClassThatUsesACalculator();
        calculator.ProgressChanged += (s, args) =>
        {
            progress.Report(new CalculatorProgress(0));
        };
        await calculator.DoWorkAsync();
    }
    

现在,在我看来,最后一个选择是最好的,因为我会保持更多的控制权。但也许我还差得很远,我想听听别人对此的意见或建议,因为我只能找到关于如何使用异步的解释,但从来没有真正找到如何构建供他人使用的方法。

使用async/await的最佳实践

一般来说,将任何Task.Run用法尽可能向上推到调用堆栈中。

您希望避免的是拥有一个具有异步签名的方法,该方法在可重用组件中使用Task.Run实现。这是一个说谎的API。我有一篇关于这个主题的博客文章更详细。

如果您控制有问题的类,我建议使用IProgress<T>而不是事件来更新进度。IProgress<T>可以很好地处理同步代码和异步代码:

public void DoWork(IProgress<CalculatorProgress> progress = null)
{
  for (int i = 0; i < 10; i++)
  {
    var result = calculator.Calculate();
    DoSomethingWithCalculationResult(result);
    DoLightWork();
    if (progress != null)
      progress.Report(new CalculatorProgress(...));
  }
}

然后使用它非常简单:

private async void MethodTwo(object sender, EventArgs e)
{
  IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress>(UpdateProgressBar);
  var calculator = new ClassThatUsesACalculator();
  await Task.Run(() => calculator.DoWork(progress));
}

这将Task.Run的使用保留在需要它的组件(UI层)中,而不在业务逻辑中。