问题
I have a list of running tasks with the same CancellationTokenSource
.
I want the current thread to wait until all the tasks complete or until the tasks were cancelled.
Task.WaitAll(tasks.ToArray(), searchCencellationTokenSource.Token);
System.Console.WriteLine("Done !");
The tasks might be cancelled by another task even when the current thread is in a waiting state. This is normal behavior.
However, while the current thread is in waiting state and another task cancel the tasks, the WaitAll throws CancellationTokenSource
with a message: "The operation was canceled.".
I know it was cancelled, I did it intentionally. I just want it to continue to the next code after the tasks were cancelled or completed, without throwing an exception.
I know I can wrap this code with try & catch but throwing an exception is heavy operation and I don't want it to happen on a normal behavior like this.
回答1:
This blocking mechanism can be rephrased as:
Task.WhenAll(taskA, taskB, taskC).Wait()
This gives you a task back which we can await but can also manage cancellation from. So, to ignore cancellation exception, you can do the following:
Task.WhenAll(taskA, taskB, taskC).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled).Wait();
Which won't throw an OperationCancelledException
.
This could then be wrapped into an extension method as follows:
public static class TaskExtensions
{
public static Task IgnoreCancellation(this Task task)
{
return task.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
}
}
Which would allow you to write the following, again without encountering an OperationCancelledException
:
Task.WhenAll(taskA, taskB, taskC).IgnoreCancellation().Wait();
Here's a test fixture showing the approach working:
public class IgnoreTaskCancellation
{
[Fact]
public void ShouldThrowAnAggregateException()
{
CancellationTokenSource cts = new CancellationTokenSource(10);
Task taskA = Task.Delay(20, cts.Token);
Task taskB = Task.Delay(20, cts.Token);
Task taskC = Task.Delay(20, cts.Token);
Assert.Throws<AggregateException>(() => Task.WhenAll(taskA, taskB, taskC).Wait());
}
[Fact]
public void ShouldNotThrowAnException()
{
CancellationTokenSource cts = new CancellationTokenSource(10);
Task taskA = Task.Delay(20, cts.Token);
Task taskB = Task.Delay(20, cts.Token);
Task taskC = Task.Delay(20, cts.Token);
Task.WhenAll(taskA, taskB, taskC).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled).Wait();
}
[Fact]
public void ShouldNotThrowAnExceptionUsingIgnore()
{
CancellationTokenSource cts = new CancellationTokenSource(10);
Task taskA = Task.Delay(20, cts.Token);
Task taskB = Task.Delay(20, cts.Token);
Task taskC = Task.Delay(20, cts.Token);
Task.WhenAll(taskA, taskB, taskC).IgnoreCancellation().Wait();
}
}
Hope it helps.
来源:https://stackoverflow.com/questions/40595720/task-waitall-throws-operationcanceledexception