Async/Await with a WinForms ProgressBar - c#

I've gotten this type of thing working in the past with a BackgroundWorker, but I want to use the new async/await approach of .NET 4.5. I may be barking up the wrong tree. Please advise.
Goal: Create a component that will do some long-running work and show a modal form with a progress bar as it's doing the work. The component will get the handle to a window to block interaction while it's executing the long-running work.
Status: See the code below. I thought I was doing well until I tried interacting with the windows. If I leave things alone (i.e. don't touch!), everything runs "perfectly", but if I do so much as click on either window the program hangs after the long-running work ends. Actual interactions (dragging) are ignored as though the UI thread is blocked.
Questions: Can my code be fixed fairly easily? If so, how? Or, should I be using a different approach (e.g. BackgroundWorker)?
Code (Form1 is a standard form with a ProgressBar and a public method, UpdateProgress, that sets the ProgressBar's Value):
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting..");
var mgr = new Manager();
mgr.GoAsync();
Console.WriteLine("..Ended");
Console.ReadKey();
}
}
class Manager
{
private static Form1 _progressForm;
public async void GoAsync()
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
_progressForm.Show(owner);
await Go();
_progressForm.Hide();
}
private async Task<bool> Go()
{
var job = new LongJob();
job.OnProgress += job_OnProgress;
job.Spin();
return true;
}
void job_OnProgress(int percent)
{
_progressForm.UpdateProgress(percent);
}
}
class LongJob
{
public event Progressed OnProgress;
public delegate void Progressed(int percent);
public void Spin()
{
for (var i = 1; i <= 100; i++)
{
Thread.Sleep(25);
if (OnProgress != null)
{
OnProgress(i);
}
}
}
}
class Win32Window : IWin32Window
{
private readonly IntPtr _hwnd;
public Win32Window(IntPtr handle)
{
_hwnd = handle;
}
public IntPtr Handle
{
get
{
return _hwnd;
}
}
}
}

The async and await keywords do not mean "run on a background thread." I have an async/await intro on my blog that describes what they do mean. You must explicitly place CPU-bound operations on a background thread, e.g., Task.Run.
Also, the Task-based Asynchronous Pattern documentation describes the common approaches with async code, e.g., progress reporting.
class Manager
{
private static Form1 _progressForm;
public async Task GoAsync()
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
_progressForm.Show(owner);
var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));
await Go(progress);
_progressForm.Hide();
}
private Task<bool> Go(IProgress<int> progress)
{
return Task.Run(() =>
{
var job = new LongJob();
job.Spin(progress);
return true;
});
}
}
class LongJob
{
public void Spin(IProgress<int> progress)
{
for (var i = 1; i <= 100; i++)
{
Thread.Sleep(25);
if (progress != null)
{
progress.Report(i);
}
}
}
}
Note that the Progress<T> type properly handles thread marshaling, so there's no need for marshaling within Form1.UpdateProgress.

#StephenCleary's answer is correct. Though, I had to make a little modification to his answer to get the behavior what I think OP wants.
public void GoAsync() //no longer async as it blocks on Appication.Run
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));
_progressForm.Activated += async (sender, args) =>
{
await Go(progress);
_progressForm.Close();
};
Application.Run(_progressForm);
}

private async void button1_Click(object sender, EventArgs e)
{
IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value; });
await Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
progress.Report(i);
});
}
Correct me if I'm wrong, but this seems to be the easiest way to update a progress bar.

Related

Updating Winforms Label with Timer and Thread, stock app

Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)

Using threads to run code after ShowDialog()

I'm trying to make a progress bar for my project but while the work is being done they shouldn't be able to interact with the main window (this is why it needs to be ShowDialog). So I've came with this solution but due to having zero multi-threading experience I don't know if this is good solution.
This is just some model code to represent the work that needs to be done and works like it's meant to.
private async void Button_Click(object sender, RoutedEventArgs e)
{
Window1 progress_Bar = new Window1();
Thread test = new Thread(() => test_method(progress_Bar));
test.Start();
for (int i = 0; i < 11; i++)
{
//work
progress_Bar.Bar.Value = i;
await Task.Delay(1000);
}
progress_Bar.Close();
test.Abort();
}
private void test_method(Window1 _Test)
{
_Test.Dispatcher.Invoke(() =>
{
_Test.ShowDialog();
});
}
Where: Window1 is the progress bar window form I'm opening and the user sees while the work is being done and .Bar is the progress bar.
So my main question, is this using threads in a secure a proper way to do asynchronous tasking?
I'd look into the IProgress T interface.
Create a class that will hold your progress information like progress value, max, min if needed..
public class MyProgressReport
{
public int ProgressValue{ get; set; }
}
And a ReportProgress method somewhere in your class which will take progress reports and update info on the UI
private void ReportProgress(MyProgressReport progressReport)
{
//your dialog window needs a property to handle progress
//or just access its Bar.Value like you do in your example
dialogWindow.Progress = progressReport.ProgressValue;
}
And finally a method for showing the dialog / doing work. Tell it what you want to do with an Action.
public async void ShowBusyWindow(Action<IProgress<MyProgressReport>> operation)
{
//ReportProgress method will be called every time you want to update progress
var progressCallback = new Progress<MyProgressReport>(ReportProgress);
progressWindow = new Window();
var workTask = DoWork(operation(progressCallback));
progressWindow.ShowDialog(); //program will wait here until window is closed
await workTask; // usually not needed since workTask will be done by the time this is hit, this is where exceptions will be thrown
}
private async Task DoWork(Action operation)
{
//you should also check for exceptions here and close window
await Task.Run(operation);
//close window when done
progressWindow.Close();
}
Finally, to use it, you would just call Show like so:
ShowBusyWindow((progress) =>
{
//do work here and update progress
var report = new MyProgressReport();
for(int i=0; i<100; i++)
{
report.ProgressValue = i;
progress.Report(report);
Thread.Sleep(500);
}
});
I haven't run this (mistakes may be present) but I use an almost identical approach with some more properties on the progress report class and exception handling.
You need an async ShowDialog method for that
public static class WindowExtensions
{
public static async Task<bool?> ShowDialogAsync(this Window window)
{
await Task.Yield(); // this is the magic ;o)
return window.ShowDialog();
}
}
and now you can do
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click( object sender, RoutedEventArgs e )
{
var dialog = new WaitWindow();
var task = WorkAsync( dialog.Progress );
var dialogTask = dialog.ShowDialogAsync();
await task;
dialog.Close();
await dialogTask;
}
private async Task WorkAsync( IProgress<double> progress )
{
for ( int i = 0; i < 100; i++ )
{
progress.Report( i );
await Task.Delay( 25 ).ConfigureAwait( false );
}
}
}
the WaitWindow class
public partial class WaitWindow : Window
{
public WaitWindow()
{
InitializeComponent();
Progress = new Progress<double>( progress => ProgressHandler( progress ) );
}
public IProgress<double> Progress { get; }
private void ProgressHandler( double progress )
{
progressBar.Value = progress;
}
}

Exiting from async infinite loops

I have initiated some async infinite loops in my WinForm application, but each time I am trying to break out of them, the program hangs up. I have read some similar topics where people suggested using CancellationTokens, but I am not able to adapt them to my needs. Here is the relevant part of my code.
static bool processStop = false;
static bool processStopped = false;
//Called once
private async void ProcessData()
{
while (!processStop)
{
await Task.Run
(
() =>
{
//Do stuff and call regular not async methods
}
);
}
processStopped = true;
}
//Button click handler to exit WinForm
btnExit.Click += (senders, args) =>
{
processStop = true;
//Programm hangs up here
while (!processStopped);
FormMain.Close();
}
Edited the code
The variables are static.
The Close method is the default Close() method for Forms.
The problem is that the call to Task.Run continues on the main thread. processStop = true; and while (!processStopped); execute synchronously one after the other. This doesn't let the ProcessData method continue its execution and a deadlock occures.
I see a couple of solutions:
Use ConfigureAwait(false) with Task.Run:
private async void ProcessData()
{
while (!processStop)
{
await Task.Run
(
() =>
{
//Do stuff and call regular not async methods
}
).ConfigureAwait(false);
}
processStopped = true;
}
This will cause the ProcessData to continue on a thread pool and you already use a thread pool by calling Task.Run, so it is not a great solution
Wrap the whole process in Task.Run:
static volatile bool processStop = false;
static volatile bool processStopped = false;
//Called once
private async void ProcessData()
{
await Task.Run(() =>
{
while (!processStop)
{
...
}
processStopped = true;
});
}
This would require changing the form of the method passed to work with the loop in it.
Make ProcessData a synchronous method to process CPU-intensive tasks and call it properly. CancellationToken would be the preferred way to cancel the task:
private void ProcessData(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
// do work
}
}
And call it with this:
Task processingTask;
CancellationTokenSource cts;
void StartProcessing()
{
cts = new CancellationTokenSource();
processingTask = Task.Run(() => ProcessData(cts.Token), cts.Token);
}
btnExit.Click += async (senders, args) =>
{
cts.Cancel();
try
{
await processingTask;
}
finally
{
FormMain.Close();
}
}
If you want to spin a bunch of tasks without blocking you can do this:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Called once
private async Task ProcessData()
{
int count = 0;
while (true)
{
await Task.Run
(
() =>
{
this.Invoke(new Action(() => {
label2.Text = (count++).ToString();
label1.Text = DateTime.Now.ToString(); }));
Thread.Sleep(100);
}
);
}
Debugger.Break(); //you will never see this hit at all
}
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
private async void button2_Click(object sender, EventArgs e)
{
await ProcessData();
}
}
}

C# (WPF) Async Thread with interface to GUI

thanks for reading this topic.
For a new WPF application (build in C#) I have a question regarding the design.
The past few days I have read a lot about Async programming in C# (based on .NET 4.5).
What we would like to do is: Create a new async thread, which does independent background tasks. When this thread has data available: then send this data to the main program (by an public interface). So, the thread will set data in the main program and immediately return to the thread again. The main program will raise an event (INotifyPropertyChanged) when data has been changed.
What will be the best way to create this Async thread? Or at least, what would be the best way to design this feature?
At the moment I have build an application which creates a thread.
This does not work Async at the moment:
public MainWindow()
{
InitializeComponent();
InitGuiInterface(this);
//Create thread
new OuterLabel_Thread(this);
}
And the class "OuterLabel_Thread.cs"here below:
public class OuterLabel_Thread
{
private MainWindow context = null;
private bool exit = false;
private int count = 0;
public OuterLabel_Thread(MainWindow context)
{
this.context = context;
Console.WriteLine("Running sample thread");
Thread thread = new Thread(delegate ()
{
Console.WriteLine("Sample thread started");
//start new task
//run();
Task.Factory.StartNew(run);
});
thread.Start();
}
public void Exit()
{
exit = true;
}
private void run()
{
while (!exit)
{
DateTime Time1 = DateTime.Now;
if (context != null && context.GuiInterface != null)
{
//context.GuiInterface.UpdateThreadCount(count, "label_code_content");
}
Console.WriteLine("Background thread count = " + count);
count++;
if (count > 1000)
{
exit = true;
}
//Console.WriteLine((DateTime.Now - Time1).TotalMilliseconds.ToString());
Thread.Sleep(10);
}
}
}
Many thanks in advance!
Kind regards,
Rein.
as you want to keep the thread alive and as far as I understand, you don't know exactly when or if you will reach the 1000 mark, async might be the wrong choice. Correct me if i'm wrong.
For your case I would recommend using the BackgroundWorker:
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int count = 0;
BackgroundWorker worker = sender as BackgroundWorker;
while (!exit)
{
DateTime Time1 = DateTime.Now;
worker.ReportProgress(count);
count++;
if (count > 1000)
{
exit = true;
}
Thread.Sleep(10);
}
}
// This event handler updates the progress.
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = ("Background thread count = " + e.ProgressPercentage.ToString());
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
resultLabel.Text = "Canceled!";
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " + e.Error.Message;
}
else
{
resultLabel.Text = "Done!";
}
}
The best way would be using async+await and tasks.
private async void LaunchButton_OnClick(object sender, RoutedEventArgs e)
{
resultLabel.Content = "Task running";
resultLabel.Content = await SomeLongRunningTaskAsync();
}
private Task<string> SomeLongRunningTaskAsync()
{
return Task.Run(
() =>
{
// Put your background work in here. with Task.Run it's not going to run on UI
int count = 0;
while (count < 1000)
{
count++;
Thread.Sleep(10);
}
return "Task done";
});
}
I can't figure out if you are looking for a service or a long running task.
Since the others have good examples of long running tasks I've made a Service
It uses some advanced concpets like SynchronizationContext that you should read up on before using this in production code. Google async await and Stephen Cleary.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var foo = new FooService();
foo.StartService(); // UI thrad calling
}
}
public class FooService
{
private SynchronizationContext _context;
private CancellationTokenSource _cts;
private CancellationToken _token;
private Task _task;
public void StartService()
{
_context = SynchronizationContext.Current; // Depends on the UI thread being the one to start the service or this will fail
_cts = new CancellationTokenSource(10000); // Run for 10 seconds
_token = _cts.Token;
_task = Task.Run(() => Run(), _token);
}
public async Task Stop()
{
_cts.Cancel();
await _task; // wait for task to finish
}
private void Run()
{
while (!_token.IsCancellationRequested)
{
// Do work
Thread.Sleep(1000);
// Alternative use Control.Invoke() if you have access to a UI element, to delegate to the UI thread
_context.Send((id) => Console.WriteLine($"Delegate from thread {id} to thread {Thread.CurrentThread.ManagedThreadId}"), Thread.CurrentThread.ManagedThreadId);
}
}
}

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 i.e. For a button (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.
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.
or,
Encapsulate the LongRunningCalculation() on the Calculator class with a Task.Run().
public Task<CalculatorResult> CalculateAsync()
{
return Task.Run(() =>
{
return LongRunningCalculation();
});
}
Create an async method on ClassThatUsesACalculator the calls that awaits 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 on 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 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.
What you want to avoid is having a method with an asynchronous signature that is implemented using Task.Run in a reusable component. That's a lying 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> works just fine 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 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.

Categories