SoFunction
Updated on 2025-03-08

Detailed explanation of the usage of C# CancellationToken and CancellationTokenSource

CancellationToken

CancellationToken has a constructor that can pass in a bool type to indicate whether the current CancellationToken is cancelled. In addition, because CancellationToken is a structure, it also has a constructor with empty parameters.

    public CancellationToken();//Because it is a structure, there is a null constructor, but it has no effect    public CancellationToken(bool canceled);

The attributes are as follows:

    // Static property, get an empty CancellationToken. The callback method registered by this CancellationToken will not be triggered, and its function is similar to the CancellationToken obtained by using an empty constructor.    public static CancellationToken None { get; }
    // Indicates whether the current CancellationToken can be cancelled    public bool CanBeCanceled { get; }
    // Indicates whether the current CancellationToken is already cancelled    public bool IsCancellationRequested { get; }
    //WaitHandle object associated with CancellationToken, the callback method registered by CancellationToken is implemented through this WaitHandle when executing    public WaitHandle WaitHandle { get; }

Common methods:

    //Register callback in CancellationToken    public CancellationTokenRegistration Register(Action callback);
    public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext);
    public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state);
    public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state, bool useSynchronizationContext);
    // When the CancellationToken is in the cancel state, an exception is thrown    public void ThrowIfCancellationRequested();

The commonly used method of registering a callback is the above four Register methods, where callback is the delegate for execution of the callback, useSynchronizationContext indicates whether to use the synchronization context, and state is the parameter value passed to the callback delegation.

In addition, the Register method will return a CancellationTokenRegistration structure. After registering the callback, you can call the Unregister method of CancellationTokenRegistration to cancel the registration. This Unregister method will return a bool value, and will return true when it is successfully cancelled. When the cancellation fails (such as the callback has been executed), it will return false:

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var cancellationTokenRegistration = (() =>
    {
        ("Canceled");//The output will not be executed here    });

    //();
    //var result = ();//result = false

    var result = ();//result = true
 ();

As mentioned above, the CancellationToken can be constructed directly using the constructor, and a parameter can be passed in to represent the current state. It should be noted that the state of the CancellationToken can be changed at most once, that is, it has never been cancelled and has been cancelled.

If true is passed during construction, that is, the CancellationToken has been cancelled, the registered callback will be executed immediately:

    CancellationToken cancellationToken = new CancellationToken(true);
    (() =>
    {
        ("Canceled");//The output Canceled will be executed immediately here    });

However, if false is passed in during construction, it means that the CancellationToken is in an uncanceled state. At this time, the registration will be in a state to be triggered:

    CancellationToken cancellationToken = new CancellationToken(false);
    (() =>
    {
        ("Canceled");//The output will not be executed immediately here    });

The service registered through the Register method will only be executed once!

But generally, if you pass in the CancellationToken constructed by false, it can be considered that it will not be triggered because it has no triggering method! So in general, we will not use the constructor to create a CancellationToken directly, but use the CancellationTokenSource object to obtain a CancellationToken.

CancellationTokenSource

CancellationTokenSource can be understood as the controller of CancellationToken, which controls when it becomes an object with cancellation status. It has a property token of CancellationToken type. As long as CancellationTokenSource is created, this token will also be created. At the same time, the token will be bound to this CancellationTokenSource.

    // Indicates whether the token is already in canceled state    public bool IsCancellationRequested { get; }
    //CancellationToken object    public CancellationToken Token { get; }

You can directly create a CancellationTokenSource object and specify a time period at the same time. After this period of time, the CancellationTokenSource will be automatically cancelled.

There are 4 ways to cancel CancellationTokenSource:

    //Cancel now    public void Cancel();
    //Cancel now    public void Cancel(bool throwOnFirstException);
    //Cancel after delaying the specified time    public void CancelAfter(int millisecondsDelay);
    //Cancel after delaying the specified time    public void CancelAfter(TimeSpan delay);

There is nothing special about Cancel and the two CancelAfter methods, mainly because they have a delay effect. It should be noted that the difference between the two overloads of Cancel.

First of all, as mentioned above, the CancellationToken status can only be changed once (never cancelled to cancelled). When the CancellationToken is cancelled, each callback registered in it will be executed immediately! When in the uncancel state, the registered callbacks will wait for execution.

It should be noted that when multiple callbacks are registered in an uncanceled state, they are in a stack-like structure sequence when executed, registering first and then executing.

The CancellationToken's Register can register multiple callbacks, and they may throw exceptions. The throwOnFirstException parameter indicates the processing behavior when the first error is reported.

throwOnFirstException = true means that the current exception is thrown immediately, and subsequent callbacks will cancel the execution.

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    try
    {
        (() =>
        {
            throw new Exception("1");
        });
        (() =>
        {
            throw new Exception("2");//Not executed        });

        (true);
    }
    catch (Exception ex)
    {
        //ex is ("1")
    }

throwOnFirstException = false indicates that the exception of the current callback is skipped, and the effective callback is continued. After all callbacks are executed, then package all exceptions into an exception and throw them out!

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    try
    {
        (() =>
        {
            throw new Exception("1");
        });
        (() =>
        {
            throw new Exception("2");
        });

        (false);//Equivalent to ()    }
    catch (Exception ex)
    {
        //ex is :[Exception("2"),Exception("1")]
    }

CancellationTokenSource can also be associated with other CancellationTokens to generate a new CancellationToken. When other CancellationTokens are cancelled, the current CancellationTokenSource will be automatically triggered to perform the cancellation action!

Use scenario 1

When we create an asynchronous operation, we can pass in a CancellationToken. When the asynchronous operation is in a waiting state, we can cancel the execution of the asynchronous operation by setting the CancellationToken to the cancel state:

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var task = new Task(() =>
    {
        (1500);//Execute the code for 2 seconds        ("Execute Some Code");
    }, );

    ();//Start, wait for schedule execution
    //If you find something wrong, you can cancel the task execution    ();
    (1000);//Wait for 1 second    ("Task status:" + );//Canceled

But often, our cancellation action may not be so timely. If the asynchronous operation has been executed and the cancellation is invalid, we need to detect it ourselves in the asynchronous delegation:

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var task = new Task(() =>
    {
        (1500);//Execute the code for 2 seconds        ();
        ("Execute Some Code");
    }, );

    ();//Start, wait for schedule execution
    (1000);///// After a period of time, you can cancel the task execution    ();
    (1000);//Wait for 1 second    ("Task status:" + );//Canceled

Use scenario 2

Sometimes, we hope that after a certain time is triggered, we can execute certain code functions, but in an asynchronous environment, we cannot guarantee whether the code to be executed is ready. For example, we have a Close method, which means it is closed when calling Close. If we execute some notifications when the program is in a closed state, generally, we may think of using an event model, or passing in event delegation in the Close method, or using some models such as template design to implement it:

    class Demo
    {
        public void Close(Action callback)
        {
            //closure            (3000);

            callback?.Invoke();//Execution notification        }
    }

or

    class Demo
    {
        public event Action Callback;

        public void Close()
        {
            //closure            (3000);

            Callback?.Invoke();//Execution notification        }
    }

But there is a problem. If you pass in parameters or use event model, as mentioned earlier, if in an asynchronous environment, we cannot guarantee whether the code to be executed is ready. Perhaps the program has not registered a callback when executing the Close method.

At this time, you can use CancellationToken to solve this problem:

The master needs to register a callback in the Token attribute without paying attention to when Close is executed

Use scenario 3

Sometimes, we write an asynchronous infinite loop method to deal with some problems, and we hope that when the method is stopped outside, we can achieve it by returning CancellationTokenSource:

        public CancellationTokenSource Listen()
        {
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

            //Cyclic scheduling execution            (() =>
            {
                while (true)
                {
                    ();

                    //Change some operations                    (1000);
                    ("Run");
                }
            });

            return cancellationTokenSource;
        }

The above is a detailed explanation of the usage of C# CancellationToken and CancellationTokenSource. For more information about C# CancellationToken and CancellationTokenSource, please follow my other related articles!