c# - 使用 async/await 的最佳实践

标签 c# async-await

假设我有以下类定义:

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() 中完成的工作,在表单上,​​我可以异步添加一个返回任务的方法 (GetCalculationTask) Task.Run() 并添加一个异步事件处理程序,即对于一个按钮 (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() 封装 Calculator 类上的 LongRunningCalculation()

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

现在,在我看来,最后一个选项是最好的,因为我会保持更多的控制权。但也许我离题太远了,希望有人对此提出意见或指示,因为我只能找到关于如何使用异步的解释,但从来没有真正找到如何构建供其他人使用的方法。

最佳答案

作为一般规则,推送任何 Task.Run尽可能在调用堆栈中使用。

您要避免的是具有使用Task.Run 实现的异步签名的方法。在可重用的组件中。那是一个说谎的 API。我有一个 blog post on the subject更详细。

如果您控制相关类,我建议使用 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 层)和业务逻辑之外使用。

关于c# - 使用 async/await 的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25552860/

相关文章:

asp.net - Web API服务-如何在异步任务中使用 "HttpContext.Current"

c# - Unity3D : Cannot change scripting backend to . 网络

c# - LINQ 从数据表中选择

c# - Mahapps 1.3 对话框和 Avalon.Wizard

python - 强制异步函数在下一个任务之前完成

c# - 异步等待 vs GetAwaiter().GetResult() 和回调

c# - 在 asp.net 中使用 ajax 访问 C# 方法?

使用生成器的 C# 构造对象

Angular Material / Jasmine 测试 - 加载 Material 线束时出错

javascript - 如何在继续之前等待所有 promise 被解决