C # tasks are not canceled

advertisements

I have several tasks to execute. Each task completes its execution in different duration. Some of the tasks perform database access, some of them just makes some calculations. My code has the following structure:

var Canceller = new CancellationTokenSource();

List<Task<int>> tasks = new List<Task<int>>();

tasks.Add(new Task<int>(() => { Thread.Sleep(3000); Console.WriteLine("{0}: {1}", DateTime.Now, 3); return 3; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(1000); Console.WriteLine("{0}: {1}", DateTime.Now, 1); return 1; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(2000); Console.WriteLine("{0}: {1}", DateTime.Now, 2); return 2; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(8000); Console.WriteLine("{0}: {1}", DateTime.Now, 8); return 8; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(6000); Console.WriteLine("{0}: {1}", DateTime.Now, 6); return 6; }, Canceller.Token));

tasks.ForEach(x => x.Start());

bool Result = Task.WaitAll(tasks.Select(x => x).ToArray(), 3000);

Console.WriteLine(Result);

Canceller.Cancel();

tasks.ToList().ForEach(x => { x.Dispose(); }); // Exception here
tasks.Clear();
tasks = null;

Canceller.Dispose();
Canceller = null;

I have a period of 5 seconds to start all these tasks. In every 5 seconds I call the code above. Before the next call I have to be sure that no task remains from the previous execution period. Let's say if 3 seconds are passed after the execution I would like to cancel execution of tasks which are not completed.

when I run the code Task.WaitAll parameter of 3000 lets first 3 tasks are completed as expected. Then I get Result as false because 2 other tasks are not completed. Then I must cancel these two tasks. If I try to dispose them I get exception saying "Tasks in completed state can only be disposed."

How can I achieve this? After I call Cancel method of CancellationTokenSource these two tasks are still executed. What is wrong here?


First, you should almost never use Task.Start. Use the static Task.Run method instead.

When you pass a CancellationToken to Task.Run or other APIs that create tasks, this does not allow you to abort the task immediately by requesting cancellation. This just only sets the status of the task to Canceled if the code in the task throws a OperationCanceledException exception. Please take a look at the CancellationToken section of this article.

To cancel a task, the code that the task runs must cooperate with you. For example, if the code does something in a loop, then that code must check periodically if cancellation is requested and throw an exception if so (or simply exit the loop if you don't want the task to be considered cancelled). There is a method in CancellationToken called ThrowIfCancellationRequested that does just that. This of course means that such code needs to have access to the CancellationToken object. This is why we have methods that accept cancellation tokens.

As another example, if the code that the task runs calls a database access method, you'd better call a method that accepts a CancellationToken so that such method will try to exit as soon as cancellation is requested.

So in summary, cancelling an operation is not a magical thing as the code that the task runs need to cooperate.