Caustix
Caustix

Reputation: 571

Multi-threaded C# for responsive GUI with complex background calculations - async / await, TPL, or both?

I have a single-threaded program which executes complex, time consuming calculations and reports the progress of these.

    private void BtnExecute_Click()
    {
        ComplexCalculation(Object1);
        ComplexCalculation(Object2);
        ComplexCalculation(Object3);
    }

The ComplexCalculation method looks like this:

private void ComplexCalculation(Object MyObject)
{
   (...do some time consuming operation on MyObject...);
   WriteToTextboxGUI("50% Complete" + MyObject.Name);
   (...do another time consuming operation on MyObject...);
   WriteToTextboxGUI("100% Complete" + MyObject.Name);
}

In the above code, the WriteToTextboxGUI(string) method updates a textbox on the GUI with the progress.

I'm looking to adopt a multi-threaded approach so that the GUI remains responsive while the complex calculations are carried out. I have read up quite a bit about BackgroundWorker and Threads in general, and then how this was simplified/improved when Task and TPL came onto the scene with .Net 4.0, and now how Async and Await have arrived with .Net 4.5, and would like to know if these more recent technologies can allow me to re-write my application (in relatively simple code) so that it can:

Can anyone can point me in the direction of a simple solution which would satisfy these three criteria?

P.S. This application will not be running on a server, it will be running in WPF on the desktop.

Upvotes: 1

Views: 1116

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457057

I recommend that you use IProgress<T> for progress reports and Task.Run to start background tasks. I recently finished a blog series showing that Task.Run is superior to BackgroundWorker.

In your case:

private sealed class ComplexCalculationProgress
{
  public string Name { get; set; }
  public int PercentComplete { get; set; }
}

private void ComplexCalculation(Object MyObject, IProgress<ComplexCalculationProgress> progress)
{
  (...do some time consuming operation on MyObject...);
  if (progress != null)
    progress.Report(new ComplexCalculationProgress { Name = MyObject.Name, PercentComplete = 50 });
  (...do another time consuming operation on MyObject...);
  if (progress != null)
    progress.Report(new ComplexCalculationProgress { Name = MyObject.Name, PercentComplete = 100 });
}

Note that since your background operation no longer calls WriteToTextboxGUI, it has better separation of concerns. You'll find that the design of IProgress<T> encourages a better design in your own code.

You can call it (using simple parallelism) as such:

private async void BtnExecute_Click()
{
  var progress = new Progress<ComplexCalculationProgress>(update =>
  {
    WriteToTextboxGUI(update.PercentComplete + "% Complete " + update.Name);
  });
  await Task.WhenAll(
    Task.Run(() => ComplexCalculation(Object1, progress)),
    Task.Run(() => ComplexCalculation(Object2, progress)),
    Task.Run(() => ComplexCalculation(Object3, progress))
  );
}

Alternatively, you can easily use "real" parallelism:

private async void BtnExecute_Click()
{
  var progress = new Progress<ComplexCalculationProgress>(update =>
  {
    WriteToTextboxGUI(update.PercentComplete + "% Complete " + update.Name);
  });
  var objects = new[] { Object1, Object2, Object3 };
  await Task.Run(() =>
      Parallel.ForEach(objects, o => ComplexCalculation(o, progress))
  );
}

Upvotes: 7

smead
smead

Reputation: 1808

Background workers can do all this easily.

Use multiple threads simultaneously to execute complex calculations

Use a new background worker for each calculation.

Report progress to my GUI as before

Use WorkerReportsProgress=True, with the ReportProgress() method and ProgressChanged event.

Maintain a responsive GUI throughout

Yep.

Upvotes: 0

Related Questions