Task.WaitAll throws OperationCanceledException [closed]
Asked Answered
E

1

5

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.

Egidius answered 14/11, 2016 at 18:48 Comment(10)
It's documented. Why you pass CancellationToken to WaitAll if you don't want to get an exception?Messroom
I pass it in order to cancel the waiting but, unfortunately, it throws this exception even when I don't passing it. @IvanStoevEgidius
It's also documented that you get AggregateException if at least one task is cancelled. Want or not, this is how they designed it, you have no other choice than using try/catch.Messroom
"I don't want it to happen on a normal behavior like this" -- then don't use an exception to interrupt the tasks. Without a good minimal reproducible example clearly illustrating your issue, it's impossible provide a good answer. But there's nothing in the language that forces you to throw an exception when you want your task to be interrupted. You can just return from the task without an exception and provide some other mechanism (if needed) to distinguish between a normal completion and an interruption.Sahib
Please fix your question so that you show exactly what you're doing, explain what you've already tried with respect to avoiding the problem, and what specifically you're having trouble with.Sahib
@PeterDuniho You didn't read the issue carefully, did you? I'm not throwing any exception to interrupt the task (or any exception at all). I just don't want it to throw an exception on cancelling, that's all. My description is well detailed. You wrote I throw an exception in order t interrupt the tasks, it is incorrect. Please change it. I didn't write it. Thank you.Egidius
"I'm not throwing any exception" -- prove it. Provide a good minimal reproducible example that shows that an exception occurs at the place you claim, without an exception being thrown.Sahib
I don't see any way around this. As @Ivan said, it is documented: MSDN. ibebbs' solution below is clever but, to my knowledge, it is just going to hide the exception; it does not prevent the exception from occurring though.Lycaonia
@PeterDuniho I specified all the subjects relevant to this issue. It's useless to specify the content of the tasks and all the algorithm inside. You want me to prove what I didn't write?!?! So you want me to specify an issue in the negation way. This is NOT the correct way to describe an issue. You can't find any document tells you to describe that way. Even in the documents you attached a link to.Egidius
@JasonBoyd As you written, there is no any way around this. I couldn't find any better (and maybe correct) way to do so. I'll probably add try & cath or change the whole way to work with Mutex and other types of lockers. Thank youEgidius
P
10

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.

Policeman answered 14/11, 2016 at 19:51 Comment(3)
Thank you for your answer. I think this is kind of work around. Maybe do you have such code running on production? If so, does it run OK?Egidius
@Egidius by design it throws exception but you don't want that. So you are indeed looking for a workaround.Piccoloist
It works well, thank you!Egidius

© 2022 - 2024 — McMap. All rights reserved.