user4668483
user4668483

Reputation:

CancellationToken.ThrowIfCancellationRequested not throwing

I've written a very simple app to implement some task-based asynchronous operation.

The client code calls a method which returns a Task. I pass a CancellationToken to that method but even if I call CancellationToken.ThrowIfCancellationRequested method during the process, cancelling never throws OperationCancelledException.

You can download the whole solution here, if you want to test for yourself : https://github.com/stevehemond/asynctap-example

Here is the code :

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncTapExample
{
    public partial class MainForm : Form
    {
        private const int totalSeconds = 5;

        private bool isStarted;

        public MainForm()
        {
            this.InitializeComponent();
        }

        private async void processingButton_Click(object sender, EventArgs e)
        {
            var cts = new CancellationTokenSource();

            if (!this.isStarted)
            {
                this.processingButton.Text = "Cancel";

                this.isStarted = true;

                var progressIndicator = new Progress<int>(this.ReportProgress);

                try
                {
                    await this.ProcessLongRunningOperationAsync(progressIndicator, cts.Token);

                    MessageBox.Show("Completed!");
                }
                catch (OperationCanceledException)
                {
                    MessageBox.Show("Cancelled!");
                }

                this.ResetUI();
            }
            else
            {
                cts.Cancel();
                this.processingButton.Text = "Start";
                this.isStarted = false;
            }
        }

        private void ResetUI()
        {
            this.progressBar.Value = 0;
            this.processingButton.Enabled = true;
            this.progressMessageLabel.Text = string.Empty;
            this.isStarted = false;
            this.processingButton.Text = "Start";
        }

        private Task ProcessLongRunningOperationAsync(IProgress<int> progress, CancellationToken ct)
        {
            return Task.Run(
                () =>
                    {
                        for (var i = 0; i <= totalSeconds; i++)
                        {
                            ct.ThrowIfCancellationRequested();

                            Thread.Sleep(1000);
                            progress?.Report((i * 100) / totalSeconds);
                        }
                    },
                ct);
        }

        private void ReportProgress(int progressPercentage)
        {
            this.progressBar.Value = progressPercentage;
            this.progressMessageLabel.Text = $"{progressPercentage}%";
        }
    }
}

There must be something I don't understand about passing the CancellationToken to Tasks ... I just can't figure out what.

Upvotes: 1

Views: 1214

Answers (1)

user4003407
user4003407

Reputation: 22132

You create CancellationTokenSource on every call to processingButton_Click. In result you cancel different CancellationTokenSource from what used to create task. You should create new CancellationTokenSource only when you create new task, and you should save that CancellationTokenSource, so you can cancel it:

private CancellationTokenSource cts; //MainForm instance field

private async void processingButton_Click(object sender, EventArgs e)
{
    if (!this.isStarted)
    {
        this.cts = new CancellationTokenSource();

        this.processingButton.Text = "Cancel";

        this.isStarted = true;

        var progressIndicator = new Progress<int>(this.ReportProgress);

        try
        {
            await this.ProcessLongRunningOperationAsync(progressIndicator, this.cts.Token);

            MessageBox.Show("Completed!");
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Cancelled!");
        }

        this.ResetUI();
    }
    else
    {
        this.cts.Cancel();
        this.processingButton.Text = "Start";
        this.isStarted = false;
    }
}

Upvotes: 1

Related Questions