How to execute task in the wpf background while able to provide report and allow cancellation?
Solution 1
I thought I answered your question here. If you need more sample code on how to do this using Task Parallel Library, with CancellationTokenSource
and IProgress<T>
, here it is:
Action _cancelWork;
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
this.StartButton.IsEnabled = false;
this.StopButton.IsEnabled = true;
try
{
var cancellationTokenSource = new CancellationTokenSource();
this._cancelWork = () =>
{
this.StopButton.IsEnabled = false;
cancellationTokenSource.Cancel();
};
var limit = 10;
var progressReport = new Progress<int>((i) =>
this.TextBox.Text = (100 * i / (limit-1)).ToString() + "%");
var token = cancellationTokenSource.Token;
await Task.Run(() =>
DoWork(limit, token, progressReport),
token);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
this.StartButton.IsEnabled = true;
this.StopButton.IsEnabled = false;
this._cancelWork = null;
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
this._cancelWork?.Invoke();
}
private int DoWork(
int limit,
CancellationToken token,
IProgress<int> progressReport)
{
var progress = 0;
for (int i = 0; i < limit; i++)
{
progressReport.Report(progress++);
Thread.Sleep(2000); // simulate a work item
token.ThrowIfCancellationRequested();
}
return limit;
}
Solution 2
BackgroundWorker
is what you are looking for instead of DispatcherTimer.
It provides support of Cancellation (via WorkerSupportsCancellation) and reporting progress back on UI thread (via WorkerReportsProgress).
Refer to excellent detailed article here - How to Use BackgroundWorker.
Syaiful Nizam Yahya
Updated on June 05, 2022Comments
-
Syaiful Nizam Yahya almost 2 years
I want to execute a long running task after clicking a wpf button. Here what I did.
private void Start(object sender, RoutedEventArgs e) { for (int i = 0; i < 10; i++) { Thread.Sleep(2000); // simulate task } }
Problem is, this will make wpf gui unresponsive. I also would like to allow cancellation and report progress every 1 second. I expand the code as below.
DispatcherTimer dispatcherTimer = new DispatcherTimer(); // get progress every second private int progress = 0; // for progress reporting private bool isCancelled = false; // cancellation private void Start(object sender, RoutedEventArgs e) { InitializeTimer(); // initiallize interval timer Start(10); // execute task } private void InitializeTimer() { dispatcherTimer.Tick += dispatcherTimer_Tick; dispatcherTimer.Interval = new TimeSpan(0,0,1); dispatcherTimer.Start(); } private void dispatcherTimer_Tick(object sender, EventArgs e) { Logger.Info("Current loop progress " + progress); // report progress } private void Cancel(object sender, RoutedEventArgs e) // cancel button { isCancelled = true; } private int Start(int limit) { isCancelled = true; progress = 0; for (int i = 0; i < limit; i++) { Thread.Sleep(2000); // simulate task progress = i; // for progress report if (isCancelled) // cancellation { break; } } return limit; }
My target platform is .NET 4.5. What is the recommended way to do this?
Thanks.
-
noseratio about 10 years
BackgroundWorker
can get the job done, but it's superseded with TPL: Task.Run vs BackgroundWorker. -
Rohit Vats about 10 yearsThat's completely dependent on how complex is code. Underneath both are using ThreadPool. Also
IProgress<T>
interface is available from .Net 4.5 and not before that. -
Rohit Vats about 10 years
IProgress<T>
interface is available from .Net 4.5 and not before that. -
noseratio about 10 years@RohitVats,
IProgress<T>
is available for .NET 4.0 with Microsoft.Bcl.Async, which is a production quality library. It can also be easily replaced with aDispatcher.BeginInvoke
one-liner. -
noseratio about 10 yearsI've addressed the same point about
IProgress<T>
in the comments to my answer. -
Rohit Vats about 10 yearsRight. Needs NuGet library to get it work. But how it can be replaced by
Dispatcher.BeginInvoke
? That is used to put delegates on dispatcher asynchronously. How that is related to report Progress? -
noseratio about 10 years@RohitVats,
IProgress<T>.Report
is asynchronous. When created,Progress<T>
captures the synchronization context of the UI thread. Then, whenReport
is called from the worker thread, it usesSynchronizationContext.Post
internally, notSynchronizationContext.Send
. In case withDispatcherSynchronizationContext
, thePost
is a wrapper aroundDispatcher.BeginInvoke
. The implementation ofProgress<T>
is trivial, if you check it with Reflector or a similar tool. -
Rohit Vats about 10 yearsFair enough. Now I get it what you are trying to say. It got me thinking that how
Progress<T>
can be replaced byDispatcher.BeginInvoke
. This linePost is a wrapper around Dispatcher.BeginInvoke
makes perfect sense. :) -
avo about 10 years+1 for hiding cancellation into
cancelWork
lambda. -
Syaiful Nizam Yahya about 10 yearsWhat is the recommended way to handle ThrowIfCancellationRequested?
-
noseratio about 10 years@publicENEMY, check this, it shows how to differentiate a cancellation exception from others.
-
Syaiful Nizam Yahya about 10 years@Noseratio So basically, catch OperationCanceledException and do nothing/something and carry on. By the way, +1 on Action for cancelling work. I now dont have to reset cancellation token values everytime I started Task.
-
noseratio about 10 years@publicENEMY, basically yes, but it totally depends on your ViewModel. You need to make sure that the model (and hence the UI) remains in the integral state.
-
Syaiful Nizam Yahya about 10 yearsIn the statement, await Task.Run(() => DoWork(limit, token, progressReport), token);, why token is passed twice? why progressReport isnt passed twiced? Thanks.
-
noseratio about 10 years@publicENEMY: 1) it's passed to
DoWork
so it can doThrowIfCancellationRequested
. 2) It's also passed toTask.Run
itself. The goal for this is that iftoken
is already in cancelled state, the task won't even start,Task.Run
will throwOperationCanceledException
instantly. -
Syaiful Nizam Yahya about 10 years@Noseratio Ahh. Thanks for the explanation.
-
FizxMike almost 7 yearsIs this Silverlight dependent?
-
Rohit Vats almost 7 years@FizxMike - BackgroundWorker, IProgress<T> and TPL are independent of Silverlight and WPF.