简体   繁体   English

使用async / await的最佳实践

[英]Best practice on using async / await

Say I have the following class definitions: 假设我有以下类定义:

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

If I would want to do the work done in DoWork() , on the form, asynchronously I could add a method ( GetCalculationTask ) that returns a task using Task.Run() and add a async eventhandler ie For a button ( MethodOne ). 如果我想在DoWork()完成工作,在表单上异步地我可以添加一个方法( GetCalculationTask ),它使用Task.Run()返回一个任务,并添加一个async eventhandler,即一个按钮( MethodOne )。

Please correct me if I'm wrong, but it seems to me that this would be the only option when the ClassThatUsesACalculator and Calculator classes reside in a library I don't own. 如果我错了,请纠正我,但在我看来,当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);
}

In the case I do own the library I think there are two more options, one of which very much like the first one. 在我拥有库的情况下,我认为还有两个选项,其中一个与第一个非常相似。 Probably due to the lack of my own understanding. 可能是由于缺乏我自己的理解。

Create a method on on ClassThatUsesACalculator that encapsulates the DoWork() method and then call that from an asynchronous method on the form. ClassThatUsesACalculator上创建一个封装DoWork()方法的方法,然后从表单上的异步方法调用该方法。

or, 要么,

  1. Encapsulate the LongRunningCalculation() on the Calculator class with a Task.Run() . 使用LongRunningCalculation()封装Calculator类上的Task.Run()

     public Task<CalculatorResult> CalculateAsync() { return Task.Run(() => { return LongRunningCalculation(); }); } 
  2. Create an async method on ClassThatUsesACalculator the calls that awaits the newly created method. ClassThatUsesACalculator上创建等待新创建方法的调用的异步方法。

     public async Task DoWorkAsync() { for (int i = 0; i < 10; i++) { var result = await calculator.CalculateAsync(); DoSomethingWithCalculationResult(result); DoLightWork(); OnProgressChanged(); } } 
  3. Create an asynchronous method on the form ( MethodThree ) 在窗体上创建一个异步方法( 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(); } 

Now, in my opinion the last option would be the best as I would remain more control. 现在,在我看来,最后一个选项将是最好的,因为我会保持更多的控制权。 But maybe I'm way off and would like someone's opinion or pointers on this as I can only find explanations on how to consume async, but never really how to build methods for others to consume. 但也许我已经离开了,希望得到某人的意见或指示,因为我只能找到关于如何消耗异步的解释,但从来没有真正如何构建其他人消费的方法。

As a general rule, push any Task.Run usage as far up the call stack as possible. 作为一般规则,尽可能将任何Task.Run用法推Task.Run调用堆栈。

What you want to avoid is having a method with an asynchronous signature that is implemented using Task.Run in a reusable component. 您要避免的是拥有一个带有异步签名的方法,该方法是在可重用组件中使用Task.Run实现的。 That's a lying API. 这是一个撒谎的API。 I have a blog post on the subject that goes into greater detail. 我有关于这个主题博客文章更详细。

If you control the classes in question, I recommend using IProgress<T> instead of events for progress updates. 如果您控制有问题的类,我建议使用IProgress<T>而不是事件进行更新。 IProgress<T> works just fine with synchronous code as well as asynchronous: 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(...));
  }
}

Then using it is quite straightforward: 然后使用它非常简单:

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

That keeps the Task.Run usage in the component that needs it - the UI layer - and out of the business logic. 这使得Task.Run使用保持在需要它的组件(UI层)和业务逻辑之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM