I am trying to update my toolset with the new tools offered by C# 8, and one method that seems particularly useful is a version of Task.WhenAll that returns an IAsyncEnumera
I really liked the solution provided by Panagiotis, but still wanted to get exceptions raised as they happen like in JohanP's solution.
To achieve that we can slightly modify that to try closing the channel in the continuations when a task fails:
public IAsyncEnumerable ToAsyncEnumerable(IEnumerable> inputTasks)
{
if (inputTasks == null)
{
throw new ArgumentNullException(nameof(inputTasks), "Task list must not be null.");
}
var channel = Channel.CreateUnbounded();
var channelWriter = channel.Writer;
var inputTaskContinuations = inputTasks.Select(inputTask => inputTask.ContinueWith(completedInputTask =>
{
// Check whether the task succeeded or not
if (completedInputTask.Status == TaskStatus.RanToCompletion)
{
// Write the result to the channel on successful completion
channelWriter.TryWrite(completedInputTask.Result);
}
else
{
// Complete the channel on failure to immediately communicate the failure to the caller and prevent additional results from being returned
var taskException = completedInputTask.Exception?.InnerException ?? completedInputTask?.Exception;
channelWriter.TryComplete(taskException);
}
}));
// Ensure the writer is closed after the tasks are all complete, and propagate any exceptions from the continuations
_ = Task.WhenAll(inputTaskContinuations).ContinueWith(completedInputTaskContinuationsTask => channelWriter.TryComplete(completedInputTaskContinuationsTask.Exception));
// Return the async enumerator of the channel so results are yielded to the caller as they're available
return channel.Reader.ReadAllAsync();
}
The obvious downside to this is that the first error encountered will end enumeration and prevent any other, possibly successful, results from being returned. This is a tradeoff that's acceptable for my use case, but may not be for others.