【问题标题】:Task.WhenAll not throwing exception as expectedTask.WhenAll 未按预期抛出异常
【发布时间】:2017-08-06 15:34:18
【问题描述】:

我有两个异步方法作为单独的线程/任务在表单窗口的后台运行。这些是无限循环,只是在后台做一些工作,然后使用调度程序更新 UI。见下文。

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

这些任务在单击按钮时启动(代码如下所示)。我正在尝试使用 await Task.WhenAll 来等待这两个任务。设置取消令牌后,这将按预期工作并捕获 OperationCanceledException。但是,任何由相机或 Arduino 问题引发的异常(通过在运行期间简单地拔下 USB 来模拟)似乎都不会被捕获。

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

奇怪的是,我看到输出窗口中抛出了异常,但它们并没有冒泡到我的 try/catch。此外,如果我只是等待一项任务,它会按预期工作并且异常会冒泡。

有人知道我做错了什么吗?

谢谢!

【问题讨论】:

  • @StenPetrov 是的,我期待这一点。它似乎确实引发了异常,因为我可以在输出窗口中看到它,但是在使用 .WhenAll 时它不会冒泡到我的 try/catch
  • @StenPetrov WhenAll 将抛出一个 AggregateException,其中包含来自每个出错任务的每个异常的异常,而不仅仅是第一个。
  • @servy 感谢您的澄清,我确实在最后一个示例 await Task.WhenAll 中找到了相同的语法 here,但是对于这个问题可能很重要的警告是:“任务可能是调用Task.WhenAll的结果。当你等待这样一个任务时,只会捕获一个异常,你无法预测会捕获哪个异常。”。
  • 看这里,可能有帮助codeblog.jonskeet.uk/2010/11/04/…
  • @leonhart88:问题在您发布的代码中并不明显。试试reducing to a minimal, complete example,你可能会自己发现解决方案。

标签: c# multithreading asynchronous


【解决方案1】:

这一行

await Task.WhenAll(task1, task2);

如果发生在 task1 和/或 task2 中会抛出 AggregateException,并且会包含来自内部所有任务的异常。

但要发生这种情况(即让您收到 AggregateException)所有任务应该完成它们的执行。

因此,在您当前的状态下,在两个任务中都发生异常时(迟早)会收到异常。

如果您确实需要在其中一个任务失败时停止所有其他任务,您可以尝试使用例如Task.WhenAny 而不是Task.WhenAll

另一种选择是实现一些手动同步 - 例如,引入像“wasAnyExceptions”这样的共享标志,每当该任务中发生异常时将其设置在每个任务中,并在任务循环中检查它以停止循环执行。

更新基于 cmets

为了澄清,Task.WhenAll(..) 将返回任务。此任务完成后,它将包含AggregateException,但其Exception 属性内的所有失败任务的例外情况除外。

如果您为此类任务await,它将从列表中的第一个故障任务中抛出未包装的异常。

如果您.Wait() 完成此任务,您将收到AggregateException

【讨论】:

  • 感谢@Lanorkin,您的评论帮助我找到了答案。我可以确认 Task.WhenAll 一直在等待其他任务完成。 Task.WhenAny 是我最终使用的,因为它会在任何任务引发异常时强制完成。我确实注意到,当一个任务发生异常时,Task.WhenAny 仍然不会抛出异常,它只是返回一个已完成的任务。为了使异常冒泡,您需要等待从 Task.WhenAny 返回的任务。见stackoverflow.com/questions/31544684/…
  • @leonhart88 我认为您实际上有更简单的方法 - 只需查看 WaitAny 之后的任务状态并观察 task.Exception 属性 msdn.microsoft.com/en-us/library/… 还要记住,这两个任务都可能失败,但 WaitAny 会返回只有第一个,所以你可能也想分析其他任务。我在类似的情况下使用手动同步,因为它更透明的审查和使用。
  • @Lanorkin,小修正:await Task.WhenAll(task1, task2)不会抛出 AggregateException。 await 将解开该异常并抛出第一个失败的任务的异常。 Task.WhenAll 返回的任务将是包含 AggregateException 的任务。
  • @joerage 是的,完全正确
  • @joerage 并且只是为了澄清 first 的意思不是第一次发生,而是按照您用于WhenAll 列表的顺序第一个失败的任务;因此,如果Task.WhenAll(task1, task2) 中的两个任务都失败了,无论哪个任务及时首先失败,await 将始终从task1 解包异常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
相关资源
最近更新 更多