2017-03-15 62 views
0

我有两个异步方法,我在表单窗口的背景下作为单独的线程/任务运行。这些是无限循环,只是在后台执行一些工作,然后使用调度程序更新UI。见下文。Task.WhenAll没有按预期抛出异常

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

这些任务是通过单击按钮(代码如下)启动的。我试图使用等待Task.WhenAll为了等待这两个任务。当设置取消标记时,它将按预期工作,并捕获OperationCanceledException。但是,由Camera或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。另外,如果我只是在等待一项任务,它按预期工作,异常冒泡。

任何人有任何想法我做错了什么?

谢谢!

+0

@StenPetrov是的,我期待着。它似乎会抛出一个异常,因为我可以在输出窗口中看到它,但是它在使用时不会冒泡到我的try/catch中。WhenAll – leonhart88

+1

@StenPetrov'WhenAll'会抛出一个包含异常形式的'AggregateException',形成每个发生故障的任务的每一个异常,而不仅仅是第一个异常。 – Servy

+0

@servy你确定吗?我想你必须在'WhenAll'任务上调用'Wait',如果你想捕获单个任务抛出的异常,如[这里]所示(https://msdn.microsoft.com/en-us/library/hh194874( v = vs.110)的.aspx)。但是,我不知道与“等待WhenAll”任务的关系。 – Quantic

回答

2

此行

await Task.WhenAll(task1, task2); 

如果将发生在TASK1和/或TASK2它,将包含从里面所有的任务异常扔AggregateException。

但这种情况发生(即你来领取AggregateException)所有任务要完成它们的执行。

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

如果需要停止所有其他任务,只要他们中的一个失败了,你可以尝试使用例如Task.WhenAny,而不是Task.WhenAll

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

UPDATE基于评论

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

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

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

+0

谢谢@Lanorkin,您的评论帮助我找到答案。 我可以确认Task.WhenAll不停地等待其他任务完成。 Task.WhenAny是我最终使用的,因为当任何任务抛出异常时它将强制完成。 我注意到,当一个任务发生异常时,Task.WhenAny仍然不会抛出异常,它只是返回一个完成的任务。为了向上冒泡,您需要等待Task.WhenAny返回的任务。请参阅http:// stackoverflow。com/questions/31544684/why-do-the-task-whenany-throw-an-expected-timeoutexception – leonhart88

+0

@ leonhart88我认为你实际上有更简单的方法 - 只需在WaitAny&observe task.Exception后查看任务状态属性https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.exception(v=vs.110).aspx还要记住,这两个任务都可能失败,但WaitAny只会返回第一个,所以你可能也想分析其他任务。我在类似的情况下使用手动同步,因为它是更加透明的审查和使用。 – Lanorkin

+0

@Lanorkin,小的更正:'await Task.WhenAll(task1,task2)'将*不抛出一个AggregateException。 'await'将解开这个异常,并抛出第一个失败的任务的异常。 'Task.WhenAll'返回的任务将是包含'AggregateException'的任务。 – joerage