Menelaos Vergis
Menelaos Vergis

Reputation: 3945

Ignore the Tasks throwing Exceptions at Task.WhenAll and get only the completed results

I am working on a Task parallel problem that I have many Tasks that may or may not throw Exception.

I want to process all the tasks that finishes properly and log the rest. The Task.WhenAll propage the Task exception without allowing me to gather the rest results.

    static readonly Task<string> NormalTask1 = Task.FromResult("Task result 1");
    static readonly Task<string> NormalTask2 = Task.FromResult("Task result 2");
    static readonly Task<string> ExceptionTk = Task.FromException<string>(new Exception("Bad Task"));
    var results = await Task.WhenAll(new []{ NormalTask1,NormalTask2,ExceptionTk});

The Task.WhenAll with throw the Exception of ExcceptionTk ignoring the rest results. How I can get the results ignoring the Exception and log the exception at same time?

I could wrap the task into another task that try{...}catch(){...} the internal exception but I don't have access to them and I hope I will not have to add this overhead.

Upvotes: 27

Views: 12873

Answers (3)

Theodor Zoulias
Theodor Zoulias

Reputation: 43475

You can get the result of each successfully completed Task<TResult> from its property Result.

Task<string> normalTask1 = Task.FromResult("Task result 1");
Task<string> normalTask2 = Task.FromResult("Task result 2");
Task<string> exceptionTk = Task.FromException<string>(new Exception("Bad Task"));

Task<string>[] tasks = new[] { normalTask1, normalTask2, exceptionTk };
Task whenAll = Task.WhenAll(tasks);
try
{
    await whenAll;
}
catch when (whenAll.IsFaulted) // There is also the possibility of being canceled
{
    foreach (Exception ex in whenAll.Exception.InnerExceptions)
    {
        Console.WriteLine(ex); // Log each exception
    }
}

string[] results = tasks
    .Where(t => t.IsCompletedSuccessfully)
    .Select(t => t.Result)
    .ToArray();
Console.WriteLine($"Results: {String.Join(", ", results)}");

Output:

System.Exception: Bad Task  
Results: Task result 1, Task result 2  

Upvotes: 8

Support Ukraine
Support Ukraine

Reputation: 1026

You can add HOC with exception handling and then check success.

 class Program
{
    static async Task Main(string[] args)
    {
        var itemsToProcess = new[] { "one", "two" };
        var results = itemsToProcess.ToDictionary(x => x, async (item) =>
        {
            try
            {
                var result = await DoAsync();
                return ((Exception)null, result);
            }
            catch (Exception ex)
            {
                return (ex, (object)null);
            }
        });

        await Task.WhenAll(results.Values);

        foreach(var item in results)
        {
            Console.WriteLine(item.Key + (await item.Value).Item1 != null ? " Failed" : "Succeed");
        }
    }

    public static async Task<object> DoAsync()
    {
        await Task.Delay(10);
        throw new InvalidOperationException();
    }
}

Upvotes: 0

Thomas Levesque
Thomas Levesque

Reputation: 292425

You can create a method like this to use instead of Task.WhenAll:

public Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
{    
    return Task.WhenAll(
        tasks.Select(
            task => task.ContinueWith(
                t => t.IsFaulted
                    ? new ResultOrException<T>(t.Exception)
                    : new ResultOrException<T>(t.Result))));
}


public class ResultOrException<T>
{
    public ResultOrException(T result)
    {
        IsSuccess = true;
        Result = result;
    }

    public ResultOrException(Exception ex)
    {
        IsSuccess = false;
        Exception = ex;
    }

    public bool IsSuccess { get; }
    public T Result { get; }
    public Exception Exception { get; }
}

Then you can check each result to see if it was successful or not.


EDIT: the code above doesn't handle cancellation; here's an alternative implementation:

public Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
{    
    return Task.WhenAll(tasks.Select(task => WrapResultOrException(task)));
}

private async Task<ResultOrException<T>> WrapResultOrException<T>(Task<T> task)
{
    try
    {           
        var result = await task;
        return new ResultOrException<T>(result);
    }
    catch (Exception ex)
    {
        return new ResultOrException<T>(ex);
    }
}

Upvotes: 29

Related Questions