Search code examples
c#multithreadingcancellation-token

Does cancelling a CancellationToken cause a CancellationToken Exception?


I have this code and I would like to get some clarification on the use of the CancellationToken.

I read this question about the difference between using a cancellation token and a flag:

Difference between CancellationTokenSource and exit flag for Task loop exit

One thing I noticed is that it mentions nothing about Exceptions. So here's my question. If the Disappearing() method is called then will this cause a TaskCanceledException() to occur and would this be a good reason to use the CancellationToken instead of a flag?

public partial class PhrasesFrame : Frame
{
    CancellationTokenSource cts = new CancellationTokenSource();

    public PhrasesFrame(PhrasesPage phrasesPage)
    {
        Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
    }

    public void Disappearing()
    {
        cts.Cancel();
    }

    public async Task ShowCards(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            await PickCard();
        }
    }

    public async Task PickCard()
    {
        await ShowCard();
    }

    private async Task ShowCard()
    {
        await ShowPhrase();
        await ShowDetail();
    }

    private async Task ShowPhrase()
    {
        while (App.pauseCard || timer1Seconds > 0)
        {
            try
            {
                await Task.Delay(1000, tokenSource1.Token);
            }
            catch (TaskCanceledException)
            {
                // do action
                break;
            }
        }

Solution

  • CancellationTokenSource.Cancel by itself doesn't throw such exception, but it "moves" all related cancellation tokens to cancelled state. When some code notifies that cancellation token is now in cancelled state - it might throw such exception. If look at your example, this part will not throw such exception:

    public async Task ShowCards(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            await PickCard();
        }
    }
    

    Because you just don't throw it in this block. If however you instead did something like this:

    public async Task ShowCards(CancellationToken ct)
    {
        while (true)
        {
            ct.ThrowIfCancellationRequested();
            await PickCard();
        }
    }
    

    Then exception will be thrown, because, well, you throw it almost explicitly.

    Now if look at another method from your example:

    private async Task ShowPhrase()
    {
        while (App.pauseCard || timer1Seconds > 0)
        {
            try
            {
                await Task.Delay(1000, tokenSource1.Token);
            }
            catch (TaskCanceledException)
            {
                // do action
                break;
            }
        }
    }
    

    If you were awaiting Task.Delay(1000, tokenSource1.Token); and then cancel tokenSource1 - then TaskCancelledException will indeed be thrown immediately, without waiting for the whole duration of Task.Delay. This is something you cannot easily achieve with just a boolean flag. If you used Thread.Sleep(1000) and boolean flag - change to that flag won't be noticed until whole duration of sleep is finished.

    So to answer your question: in your example exception might or might not be thrown, depending on what part of code is currently executing at the moment you cancel your CancellationTokenSource (I assume that using two cancellation token sources with names cts and tokenSource1 is just a typo in your code, but if it's real code - then such exception cannot be thrown at all, because you cancel cts but Task.Delay waits on tokenSource1).