Reputation: 33
I have some async method:
public async Task<Object> MethodAsync()
{
return await MakeHttpRequestAsync();
}
I need to make multiple calls of this method. One of them can throw an exception. But I need to keep all possible returned Objects from other method calls that succeeded.
Option 1:
Task t1 = MethodAsync();
Task t2 = MethodAsync();
try
{
Task.WaitAll(new Task[]{t1,t2});
}
catch(Exception e)
{
...
}
//are t1 and t2 finished execution here with success or exception?
Option 2:
Task t1 = MethodAsync();
Task t2 = MethodAsync();
try
{
t1.Wait()
}
catch(Exception e)
{
...
}
try
{
t2.Wait()
}
catch(Exception e)
{
...
}
//what happens if t2 throws exception before t1 finished?
Any other options?
edit: Thought about third option:
public async Task<Tuple<Object, Object>> MultipleAsyncCallsAsync(some arguments)
{
Object result1 = null, result2 = null;
try
{
result1 = await MethodAsync();
}
catch(Exception e)
{
}
try
{
result2 = await MethodAsync();
}
catch (Exception e)
{
}
return Tuple.Create(result1, result2);
}
edit #2:
Looks like third option is not working, but it's working this way:
public async Task<Tuple<object, object>> MultipleAsyncCallsAsync(some arguments)
{
object result1 = null, result2 = null;
Task<object> t1 = null, t2 = null;
t1 = Task.Run(()=> MethodAsync());
t2 = Task.Run(()=> MethodAsync());
try
{
result1 = await t1;
}
catch(Exception e)
{
}
try
{
result2 = await t2;
}
catch (Exception e)
{
}
return Tuple.Create(result1, result2);
}
Upvotes: 2
Views: 1215
Reputation: 456437
Whenever there's a question about this kind of procedure ("call n methods, (a)wait for them all to complete, and then retrieve the results for each method and do something with them"), a better solution is almost always to create a higher-order method.
For example, if you want to do some kind of logging or something but otherwise ignore exceptions, then you can create a higher-order method like this:
public async Task<T> LogAndIgnoreExceptions(Task<T> task) where T : class
{
try
{
return await task;
}
catch (Exception ex)
{
Log(ex);
return null;
}
}
Then you can use Task.WhenAll
in a very natural way:
var results = await Task.WhenAll(
LogAndIgnoreExceptions(MethodAsync()),
LogAndIgnoreExceptions(MethodAsync())
);
if (results[0] is not null)
{
T result = results[0];
}
else
{
// First call had an error.
}
Or, if you want to preserve the exception details and results, what you want is a little helper type like Try
to enable railway programming:
var results = await Task.WhenAll(
Try.Create(MethodAsync()),
Try.Create(MethodAsync())
);
if (results[0].IsValue)
{
T result = results[0].Value;
}
else
{
Exception exception = results[0].Exception;
}
Upvotes: 2
Reputation: 29720
Put the tasks in an array and inspect their status like this:
var t1 = MethodAsync();
var t2 = MethodAsync();
var tasks = new[] {t1, t2};
try
{
await Task.WhenAll(tasks);
}
catch
{
var failedTasks = tasks.Where(t => t.IsFaulted);
var allExceptions = failedTasks.Select(t => t.Exception?.Flatten());
}
var results = tasks.Where(t => t.IsCompletedSuccessfully).Select(t => t.Result);
What is does is it awaits all tasks and then uses the task status (using IsCompletedSuccessfully
ect.) to get the results of all the tasks that did complete succesfully.
You can determine the failed tasks using IsFaulted
and get their exception using the tasks Exception
property.
If you want to catch/inspect the exception before the call to Task.WhenAll(..)
is completed wrap the code inside MethodAsync
in a try/catch:
public async Task<Object> MethodAsync()
{
try
{
..
}
catch(Exception ex)
{
//handle or throw
return null;
}
}
Or, if you cannot modify MethodAsync
for some reason, you can wrap the method in a try/catch using this:
Func<Task<Object>, Task<Object>> TryExecute = new Func<Task<Object>, Task<object>>(async t =>
{
try
{
return await t;
}
catch (Exception ex)
{
//handle exception
return null;
}
});
and use it like this:
var t1 = TryExecute(MethodAsync());
var t2 = TryExecute(MethodAsyncF());
var tasks = new[] {t1, t2};
await Task.WhenAll(tasks);
Upvotes: 3