Writing a Task.WhenAll/WhenAny variant that cancel

2020-07-23 15:40发布

问题:

I'm fairly new to C# and started playing around with the TPL today. I decided to write a modified version of Task Task.WhenAll as an exercise. I'd like for it to have the following behavior:

  • Upon finding the first task that has faulted or been canceled, cancel the rest of the tasks instead of waiting for them to finish.
  • If the task faulted, the returned task should have the right exception set (i.e no swallowing by continuation and replacing with OperationCancelledException())
  • No async in the method signature (want to avoid bubbling it up).

I came up with the following crazy/stupid piece of code that doesn't work and I am having a hard time visualizing what's going on. I can't imagine any blocking going on and what i envisioned happening was a chain of tasks each waiting on the rest for completion. Could someone explain what's going on?

I wouldn't have it in production code and this is just to test my fundamentals. I realize an easier way of doing this would be to do a Task.WhenAll and have the tasks in the list themselves have continuations that do the cancellation on failure.

    public static Task WhenAllError(List<Task> tasks, CancellationToken ct)
    {
        var tcs = new TaskCompletionSource<object>();
        return Task.WhenAny(tasks).ContinueWith<Task>((t) =>
             {
                 if (tasks.Count == 0)
                 {
                     tcs.SetResult(null);
                     return tcs.Task;
                 }

                 if (t.IsFaulted)
                 {
                     Console.WriteLine("Task faulted. Cancelling other tasks: {0}", t.Id);
                     cts.Cancel();
                     // Make sure the tasks are cancelled if necessary
                     tcs.SetException(t.Exception);
                     return tcs.Task;
                 }
                 // Similarly handle Cancelled

                 tasks.Remove(t);
                 return WhenAllError(tasks, ct);
             }).Unwrap();
    }

回答1:

The CancellationToken class has no Cancel method. You need a CancellationTokenSource to be able to cancel the CancellationToken.

Similarly to affect the outcome of a task you need a TaskCompletionSource you can't safely cancel already running tasks. See this post