Pablo Honey
Pablo Honey

Reputation: 1096

Why is cancelling Task.Delay slow?

I've created a test program that runs 1000 Tasks which perform Task.Delay with a random delay between 20s and 30s. It looks like cancelling this operation takes about 10s..

Here is my test program :

class Program
{
    static async Task MainAsync()
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        List<Task> allTask = new List<Task>();
        Random r = new Random(9);

        async Task SafeDelay(int delay, CancellationToken token)
        {
            try
            {
                await Task.Delay(delay, token);
            }
            catch (TaskCanceledException)
            {
            }
        }

        for (int i = 0; i < 1000; i++)
        {
            var randomDelay = r.Next(20000, 30000);
            allTask.Add(SafeDelay(randomDelay, tokenSource.Token));
            ;
        }

        Stopwatch stopwatch = new Stopwatch();

        var cancelTask = Task.Delay(1000).ContinueWith(t =>
        {
            Console.Out.WriteLine("1000ms elapsed. Cancelation request start");;
            stopwatch.Start();
            tokenSource.Cancel();
        });

        await Task.WhenAll(allTask);
        await cancelTask;

        stopwatch.Stop();

        Console.WriteLine($"Cancelation done after {stopwatch.ElapsedMilliseconds} ms");
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Started");
        Task.Run(MainAsync).GetAwaiter().GetResult();
        Console.WriteLine("End");
        Console.ReadLine();
    }
}

My Result with .NET Core 2.1:

Cancelation done after 9808ms

Why is cancelling Task.Delay so slow and is there a way to improve it?

My Result with .NET 4.7.1:

Cancelation done after 6200ms

Upvotes: 2

Views: 436

Answers (2)

Henk Holterman
Henk Holterman

Reputation: 273591

When I run this with F5 I get a similar result.
Run it with Ctrl+F5 (no debugger) and it cancels in less than 50 ms.

So you are actually timing 1000 execptions tickling the debugger. The debugger might intefere with other execution points too. Always run benchmarks in Release Mode, w/o a debugger.

Upvotes: 7

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131706

No repro with the question's code. Using it as is, I get :

Started
1000ms elapsed. Cancelation request start
Cancelation done after 38 ms
End

No repro with cleaned up code :

static async Task SafeDelay(int delay, CancellationToken token)
{
    try
    {
        await Task.Delay(delay, token);
    }
    catch (TaskCanceledException)
    {
    }
}


private static async Task Main()
{
    //Set 1000 ms timeout
    var tokenSource = new CancellationTokenSource(1000);
    var stopwatch = Stopwatch.StartNew();

    var allTask = new List<Task>();
    Random r = new Random(9); 


    for (var i = 0; i < 1000; i++)
    {
        var randomDelay = r.Next(20000, 30000);
        allTask.Add(SafeDelay(randomDelay, tokenSource.Token));
    }
    Console.WriteLine($"All {allTask.Count} tasks running after {stopwatch.ElapsedMilliseconds} ms");

    await Task.WhenAll(allTask);
    stopwatch.Stop();

    Console.WriteLine($"Cancelation done after {stopwatch.ElapsedMilliseconds} ms");
}

This produces :

All 1000 tasks running after 8 ms
Cancelation done after 1044 ms

The CancellationTokenSource has a 1000ms timeout.

Upvotes: 1

Related Questions