Reputation: 3945
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
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
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
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