2017-03-15 59 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