Using CancellationToken for timeout in Task.Run does not work

C#.NetTask Parallel-LibraryCancellationtokensourceCancellation Token

C# Problem Overview


OK, my questions is really simple. Why this code does not throw TaskCancelledException?

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationTokenSource(500).Token).Result;

    Console.WriteLine(v); // this outputs 10 - instead of throwing error.
    Console.Read();
}

But this one works

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationToken(true).Token).Result;

    Console.WriteLine(v); // this one throws
    Console.Read();
}

C# Solutions


Solution 1 - C#

Cancellation in Managed Threads:

> Cancellation is cooperative and is not forced on the listener. The listener determines how to gracefully terminate in response to a cancellation request.

You didn't write any code inside your Task.Run method to access your CancellationToken and to implement cancellation - so you effectively ignored the request for cancellation and ran to completion.

Solution 2 - C#

There is a difference in canceling a running task, and a task scheduled to run.

After the call to the Task.Run method, the task is only scheduled, and probably have not been executed yet.

When you use the Task.Run(..., CancellationToken) family of overloads with cancellation support, the cancellation token is checked when the task is about to run. If the cancellation token has IsCancellationRequested set to true at this time, an exception of the type TaskCanceledException is thrown.

If the task is already running, it is the task's responsibility to call the ThrowIfCancellationRequested method, or just throw the OperationCanceledException.

According to MSDN, it's just a convenience method for the following:

> if (token.IsCancellationRequested) > throw new OperationCanceledException(token);

Note the different kind of exception used in this two cases:

catch (TaskCanceledException ex)
{
    // Task was canceled before running.
}
catch (OperationCanceledException ex)
{
    // Task was canceled while running.
}

Also note that TaskCanceledException derives from OperationCanceledException, so you can just have one catch clause for the OperationCanceledException type:

catch (OperationCanceledException ex)
{
    if (ex is TaskCanceledException)
        // Task was canceled before running.
    // Task was canceled while running.
}

Solution 3 - C#

I think because you're not invoking the method ThrowIfCancellationRequested() from your CancellationToken object. You're ignoring, in this way, the request for the cancellation of the task.

You should do something like this:

void Main()
{
	var ct = new CancellationTokenSource(500).Token;
	 var v = 
	 Task.Run(() =>
    {
        Thread.Sleep(1000);
		ct.ThrowIfCancellationRequested();
        return 10;
    }, ct).Result;
	
    Console.WriteLine(v); //now a TaskCanceledException is thrown.
    Console.Read();
}

The second variant of your code works, because you're already initializing a token with a Canceled state set to true. Indeed, as stated here:

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true

the cancellation has already been requested and then the exception TaskCanceledException will be immediately thrown, without actually starting the task.

Solution 4 - C#

Another implementation using Task.Delay with token instead it Thread.Sleep.

 static void Main(string[] args)
    {
        var task = GetValueWithTimeout(1000);
        Console.WriteLine(task.Result);
        Console.ReadLine();
    }

    static async Task<int> GetValueWithTimeout(int milliseconds)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        cts.CancelAfter(milliseconds);
        token.ThrowIfCancellationRequested();

        var workerTask = Task.Run(async () =>
        {
            await Task.Delay(3500, token);
            return 10;
        }, token);

        try
        {
            return await workerTask;
        }
        catch (OperationCanceledException )
        {
            return 0;
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAliostadView Question on Stackoverflow
Solution 1 - C#Damien_The_UnbelieverView Answer on Stackoverflow
Solution 2 - C#George PolevoyView Answer on Stackoverflow
Solution 3 - C#Alberto SolanoView Answer on Stackoverflow
Solution 4 - C#Z.R.T.View Answer on Stackoverflow