Reputation: 135
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!
Upvotes: 3
Views: 5489
Reputation: 7504
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
.
Upvotes: 11