Reputation: 4854
I have the following code
var exceptions = new ConcurrentQueue<Exception>();
Task task = Task.Factory.StartNew(() =>
{
try
{
Parallel.Invoke(
async () => await _aViewModel.LoadData(_someId),
async () => await _bViewModel.LoadData(_someId)
);
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
}).ContinueWith((continuation) =>
{
if (exceptions.Count > 0) throw new AggregateException(exceptions);
});
I am using Task.StartNew here because the LoadData method use the Dispatcher.StartAsync method to invoke on the main UI thread internally.
The problem I have is that if I force _aViewModel.LoadData
to throw an exception it is not caught in the Catch(Exception) clause (nor if I catch AggregateException). I don't understand why!?
Upvotes: 2
Views: 1632
Reputation: 456507
Parallel.Invoke
is not async
-aware. So your async
lambdas are being converted to async void
methods, which have extremely awkward error semantics (they are not allowed to leave the async void
method; instead, they are captured and re-raised directly on the SynchronizationContext
that was active at the time the async void
method started - in this case, the thread pool).
I'm not sure why you have the Parallel.Invoke
in the first place. Since your method is already async
, you could just do something like this:
Task task = Task.Factory.StartNew(async () =>
{
try
{
Task.WaitAll(
_aViewModel.LoadData(_someId),
_bViewModel.LoadData(_someId)
);
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
})...
P.S. If you have the time, rethink the structure of this whole part of the code. Dispatcher.StartAsync
is a code smell. The UI should be (asynchronously) requesting data; the data retrieval objects should not have to know about the UI.
Upvotes: 5
Reputation: 31198
Parallel.Invoke
takes an array of Action
delegates. It has no means of knowing that your delegates are actually async
methods, and therefore it returns before your tasks have completed.
For an in-depth explanation of this behaviour, watch Lucian Wischik's Channel 9 video on the subject.
Try changing your code to use the Task.WhenAll method instead.
var aTask = _aViewModel.LoadData(_someId);
var bTask = _bViewModel.LoadData(_someId);
await Task.WhenAll(aTask, bTask);
Upvotes: 3