How to execute task in the wpf background while able to provide report and allow cancellation?

19,583

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.

Share:
19,583
Syaiful Nizam Yahya
Author by

Syaiful Nizam Yahya

Updated on June 05, 2022

Comments

  • Syaiful Nizam Yahya
    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
    noseratio about 10 years
    BackgroundWorker can get the job done, but it's superseded with TPL: Task.Run vs BackgroundWorker.
  • Rohit Vats
    Rohit Vats about 10 years
    That'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
    Rohit Vats about 10 years
    IProgress<T> interface is available from .Net 4.5 and not before that.
  • noseratio
    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 a Dispatcher.BeginInvoke one-liner.
  • noseratio
    noseratio about 10 years
    I've addressed the same point about IProgress<T> in the comments to my answer.
  • Rohit Vats
    Rohit Vats about 10 years
    Right. 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
    noseratio about 10 years
    @RohitVats, IProgress<T>.Report is asynchronous. When created, Progress<T> captures the synchronization context of the UI thread. Then, when Report is called from the worker thread, it uses SynchronizationContext.Post internally, not SynchronizationContext.Send. In case with DispatcherSynchronizationContext, the Post is a wrapper around Dispatcher.BeginInvoke. The implementation of Progress<T> is trivial, if you check it with Reflector or a similar tool.
  • Rohit Vats
    Rohit Vats about 10 years
    Fair enough. Now I get it what you are trying to say. It got me thinking that how Progress<T> can be replaced by Dispatcher.BeginInvoke. This line Post is a wrapper around Dispatcher.BeginInvoke makes perfect sense. :)
  • avo
    avo about 10 years
    +1 for hiding cancellation into cancelWork lambda.
  • Syaiful Nizam Yahya
    Syaiful Nizam Yahya about 10 years
    What is the recommended way to handle ThrowIfCancellationRequested?
  • noseratio
    noseratio about 10 years
    @publicENEMY, check this, it shows how to differentiate a cancellation exception from others.
  • Syaiful Nizam Yahya
    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
    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
    Syaiful Nizam Yahya about 10 years
    In the statement, await Task.Run(() => DoWork(limit, token, progressReport), token);, why token is passed twice? why progressReport isnt passed twiced? Thanks.
  • noseratio
    noseratio about 10 years
    @publicENEMY: 1) it's passed to DoWork so it can do ThrowIfCancellationRequested. 2) It's also passed to Task.Run itself. The goal for this is that if token is already in cancelled state, the task won't even start, Task.Run will throw OperationCanceledException instantly.
  • Syaiful Nizam Yahya
    Syaiful Nizam Yahya about 10 years
    @Noseratio Ahh. Thanks for the explanation.
  • FizxMike
    FizxMike almost 7 years
    Is this Silverlight dependent?
  • Rohit Vats
    Rohit Vats almost 7 years
    @FizxMike - BackgroundWorker, IProgress<T> and TPL are independent of Silverlight and WPF.