2

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!?

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

4 Comments

The reason I am currently using Dispatcher.StartAsync is the LoadData methods are getting data from web services, and loading into ObservableCollections which are complaining unless invoked back across to the main thread.
Treat all your data-bound objects / ViewModels (e.g., ObservableCollections) as your "logical UI". Then your "logical UI" should be requesting the data from (asynchronous) methods that return it, rather than set it as a side effect. That change in perspective will help clean up the code.
I can't quite get the syntax for returning an IEnumerable<x> from the LoadData method. They return Task<IEnumerable<x>> but using var data = _aViewModel.LoadData(_someId) doesn't compile.
@ColinDesmond: You use await to unwrap the Task. So, if LoadDataAsync returns Task<IEnumerable<x>>, then if you await that task, you'll end up with an IEnumerable<x>. Like this: IEnumerable<x> data = _aViewModel.LoadDataAsync(_someId);. You may find my async intro helpful.
3

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);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.