Search code examples
c#task-parallel-librarycancellationtokensourcecancellation-token

Why are CancellationTokens not working if local methods are used?


Given the following code you would expect that the DoWait method is executed for one second in both cases. However, it works for the first case but hangs for the second.

If we add async/await to the local method the behavior is as expected.

Given that that CTs are structs and hence passed around by value, not reference, can anyone explain explain what's going on?

using System.Diagnostics;

namespace ConsoleApp2
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            //not using local method
            using var cts1 = new CancellationTokenSource(TimeSpan.FromSeconds(1));
            Console.WriteLine(await DoWait(cts1.Token));

            //using a local method
            Task<long> LocalMethod()
            {
                using var cts1 = new CancellationTokenSource(TimeSpan.FromSeconds(1));
                return DoWait(cts1.Token);
            }

            Console.WriteLine(await LocalMethod());
        }

        static async Task<long> DoWait(CancellationToken ct)
        {
            var stopwatch = Stopwatch.StartNew();
            var elapsedMilliseconds = stopwatch.ElapsedMilliseconds;

            try
            {
                while (true)
                {
                    elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
                    ct.ThrowIfCancellationRequested();
                    await Task.Delay(10);
                }
            }
            catch (OperationCanceledException) { }
            return elapsedMilliseconds;
        }
    }
}

Solution

  • The CancellationTokenSource you're creating within LocalMehtod is being disposed when that method returns. Since it's disposed, it can no longer cancel the token you already passed to DoWait.

    In the version where you make the local method async/await, it doesn't get disposed, so can carry on working as expected.