3

I have two async methods that I am running in the background of a form window as separate threads/tasks. These are infinite loops that just do some work in the background and then update the UI using the dispatcher. See below.

 public async Task RunCameraThread(CancellationToken cancelToken) { while (true) { // If cancellation token is set, get out of the thread & throw a cancel exception cancelToken.ThrowIfCancellationRequested(); // Get an image from the camera CameraBitmap = Camera.CaptureImage(true); // Update the UI (use lock to prevent simultaneous use of Dispatcher object in other thread) lock (Dispatcher) { Dispatcher.Invoke(() => pictureBoxCamera.Image = tempBitmap); Dispatcher.Invoke(() => pictureBoxCamera.Invalidate()); } } } public async Task RunDistanceSensorThread(CancellationToken cancelToken) { while (true) { // If cancellation token is set, get out of the thread & throw a cancel exception cancelToken.ThrowIfCancellationRequested(); // Get the distance value from the distance sensor float distance = Arduino.AverageDistance(10, 100); // Update the UI (use lock to prevent simultaneous use of Dispatcher object) lock (Dispatcher) { Dispatcher.Invoke(() => textBoxDistanceSensor.Text = distance.ToString("0.00")); } } } 

These tasks are started on a button click (code shown below). I'm trying to use await Task.WhenAll in order to await both tasks. When the cancellation token is set this works as intended and an OperationCanceledException is caught. However, any exceptions thrown by issues with the Camera or Arduino (simulated by simply unplugging the USB during a run), does not seem to be caught.

 private async void buttonConnect_Click(object sender, EventArgs e) { try { // Disable UI so we cannot click other buttons DisableComponentsUI(); // Connect to Nimbus, Camera and Arduino await Task.Run(() => Nimbus.ConnectAsync()); Camera.Connect(); Camera.ManagedCam.StartCapture(); Arduino.Connect(); // Get the current Nimbus positions and enable UI UpdatePositionsUI(); EnableComponentsUI(); // Reset cancel token and start the background threads and await on them (this allows exceptions to bubble up to this try/catch statement) StopTokenSource = new CancellationTokenSource(); var task1 = Task.Run(() => RunCameraThread(StopTokenSource.Token)); var task2 = Task.Run(() => RunDistanceSensorThread(StopTokenSource.Token)); await Task.WhenAll(task1, task2); } catch (OperationCanceledException exceptionMsg) { // Nothing needed here... } catch (Hamilton.Components.TransportLayer.ObjectInterfaceCommunication.ComLinkException exceptionMsg) { NimbusExceptionHandler(exceptionMsg); } catch (FlyCapture2Managed.FC2Exception exceptionMsg) { CameraExceptionHandler(exceptionMsg); } catch (IOException exceptionMsg) { ArduinoExceptionHandler(exceptionMsg); } catch (UnauthorizedAccessException exceptionMsg) { ArduinoExceptionHandler(exceptionMsg); } catch (TimeoutException exceptionMsg) { ArduinoExceptionHandler(exceptionMsg); } } 

What's strange is that I see the exceptions thrown in the output window, but they don't bubble up to my try/catch. Also, if I simply await on one task it works as expected and the exception bubbles up.

Anyone have any idea what I'm doing wrong?

Thanks!

15
  • @StenPetrov yes I was expecting that. It does seem to throw an exception as I can see it in the output window, but it doesn't bubble up to my try/catch when using .WhenAll Commented Mar 15, 2017 at 18:24
  • 1
    @StenPetrov WhenAll will throw an AggregateException that contains the exceptions form every single exception from every task that faulted, not just the first. Commented Mar 15, 2017 at 18:40
  • 1
    @servy Thanks for clarifying, I did find the same syntax of await Task.WhenAll in the last example here, however a caveat that may be important for this question is: "the task might be the result of a call to Task.WhenAll. When you await such a task, only one of the exceptions is caught, and you can't predict which exception will be caught.". Commented Mar 15, 2017 at 19:13
  • 1
    Take a look here, it may help codeblog.jonskeet.uk/2010/11/04/… Commented Mar 15, 2017 at 19:16
  • 1
    @leonhart88: The problem isn't obvious from the code you posted. Try reducing to a minimal, complete example, and you'll probably discover the solution yourself. Commented Mar 16, 2017 at 12:59

1 Answer 1

12

This line

await Task.WhenAll(task1, task2); 

will throw AggregateException if it occurs in task1 and / or task2, and will contain exceptions from all the tasks inside.

BUT for this to occur (i.e. for you to receive AggregateException) all tasks should finish their execution.

So in your current state you will receive exception only when exceptions occurred in both tasks (sooner or later).

If you do need to stop all other tasks whenever one of them failed, you can try using for example Task.WhenAny instead of Task.WhenAll.

Another option would be to implement some manual synchronization - for example, introduce shared flag like "wasAnyExceptions", set it inside every task whenever exception in that task occur, and check it inside task loop to stop loop execution.

UPDATE based on comments

To clarify, Task.WhenAll(..) will return task. When this task is finished, it will contain AggregateException with exceptions from all failed tasks inside its Exception property.

If you await for such task it will throw unwrapped exception from the first faulted task in the list.

If you .Wait() for this task, you will receive AggregateException.

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

6 Comments

Thanks @Lanorkin, your comment helped me find the answer. I can confirm Task.WhenAll keeps waiting for the other task to finish. Task.WhenAny is what I ended up using, as it will force completion when any of the tasks throw an exception. What I did notice was that when one task had an exception, Task.WhenAny would still not throw the exception up, it just returned a completed task. In order to bubble the exceptions up, you need to await the returned task from Task.WhenAny. See stackoverflow.com/questions/31544684/…
@leonhart88 I think you have easier way actually - just take a look at task status after WaitAny & observe task.Exception property msdn.microsoft.com/en-us/library/… Also keep in mind that both tasks could fail, but WaitAny will return only the first one, so you might want to analyze the other tasks too. I in similar situation used manual synchronization as it was more transparent to review & use.
@Lanorkin, small correction: await Task.WhenAll(task1, task2) will not throw an AggregateException. await will unwrap that exception and instead throw the exception of the first task that failed. The task returned by Task.WhenAll will be the one that contains the AggregateException.
@joerage yes, exactly
@joerage and just to clarify first meaning not first in time occurrence, but first failed task in the order you used for WhenAll list; so if both tasks in Task.WhenAll(task1, task2) failed, no matter which one failed first in time, await will always unwrap exception from task1
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.