leonhart88
leonhart88

Reputation: 135

Task.WhenAll not throwing exception as expected

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

Answers (1)

Lanorkin
Lanorkin

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

Related Questions