How to implement an efficient WhenEach that streams an IAsyncEnumerable of task results?

后端 未结 4 942
闹比i
闹比i 2021-01-01 04:32

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

4条回答
  •  無奈伤痛
    2021-01-01 05:02

    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.

提交回复
热议问题