Best practice for using async / wait

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 wanted to do the work done in DoWork() in a form asynchronously, I could add a method ( GetCalculationTask ) that returns the task using Task.Run() and adds an asynchronous event handler, i.e. for the ( MethodOne ) button.

Please correct me if I am wrong, but it seems to me that this will be the only option when the ClassThatUsesACalculator and Calculator classes are in a library that I do not own.

 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 case I have a library, I think there are two more options, one of which is very similar to the first. Perhaps due to a lack of my own understanding.

Create a method on the ClassThatUsesACalculator that encapsulates the DoWork() method, and then call it from the asynchronous method on the form.

or,

  • Encapsulate LongRunningCalculation() in the Calculator class using Task.Run() .

     public Task<CalculatorResult> CalculateAsync() { return Task.Run(() => { return LongRunningCalculation(); }); } 
  • Create an asynchronous method for the ClassThatUsesACalculator calls waiting for the newly created method.

     public async Task DoWorkAsync() { for (int i = 0; i < 10; i++) { var result = await calculator.CalculateAsync(); DoSomethingWithCalculationResult(result); DoLightWork(); OnProgressChanged(); } } 
  • Create an asynchronous method in the form ( 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 latter option would be better, since I would have remained more in control. But maybe I left, and I would like someone to think or point out this, since I can only find explanations on how to consume asynchronous mode, but never know how to create methods for consumption by others.

+6
source share
1 answer

As a rule, Task.Run any use of Task.Run as Task.Run as possible to the call stack.

What you want to avoid is an asynchronous signature method that is implemented using Task.Run in the reusable component. This is a false API. I have a blog post on a topic that is described in more detail.

If you control the classes in question, I recommend using IProgress<T> instead of events to update progress. IProgress<T> works great with synchronous code as well as asynchronous:

 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 this is pretty simple:

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

This supports the use of Task.Run in the appropriate component - the user interface level - and from the business logic.

+11
source

Source: https://habr.com/ru/post/974499/


All Articles