2013-10-09 58 views
24

我想拋出並捕獲一個AggregateException。 我沒有在C#上使用異常,但我發現的行爲有點令人驚訝。捕獲AggregateException

我的代碼是:

var numbers = Enumerable.Range(0, 20); 

try 
{ 
    var parallelResult = numbers.AsParallel() 
     .Where(i => IsEven(i)); 
    parallelResult.ForAll(e => Console.WriteLine(e)); 

} 
catch (AggregateException e) 
{ 
    Console.WriteLine("There was {0} exceptions", e.InnerExceptions.Count()); 
} 

它調用函數ISEVEN

private static bool IsEven(int i) 
{ 
    if (i % 10 == 0) 
     throw new AggregateException("i"); 
    return i % 2 == 0; 
} 

拋出的AggregateException。

我希望代碼可以寫出0,20範圍內的每個偶數和「有1個例外」兩次。

我得到的是打印的一些數字(它們是ForAll的隨機原因),然後拋出異常,但沒有捕獲並且程序停止。

我錯過了什麼?

+2

我不知道爲什麼會這樣,嘗試改變'拋出新AggregateException( 「I」);'到'拋出新的ArgumentException( 「I」);'產生預期的結果 –

+0

投擲'拋出新AggregateException( 「我」 ,new [] {new ArgumentException(「i」)});'也有幫助,但不知道爲什麼你的版本崩潰了應用程序 –

+4

你通過拋出錯誤的異常混淆了系統。相反,拋出一個參數或InvalidOperationException。 –

回答

21

這其實很有趣。我認爲問題在於你以意想不到的方式使用了AggregateException,這導致了PLINQ代碼中的錯誤。

AggregateException的整個要點是將並行過程中可能同時(或幾乎如此)發生的多個異常組合在一起。所以AggregateException預計至少有一個內部異常。但你投擲new AggregateException("i"),這沒有內在的例外。 PLINQ代碼試圖檢查InnerExceptions屬性,遇到某種錯誤(可能是NullPointerException),然後它似乎進入某種類型的循環。這可以說是PLINQ中的一個錯誤,因爲你使用了一個有效的構造函數AggregateException,即使它是一個不尋常的構造函數。

正如別處指出的那樣,拋出ArgumentException在語義上更準確。但你可以得到你通過改變IsEven功能像這樣扔了正確構造的AggregateException,例如尋找行爲:

private static bool IsEven(int i) 
{ 
    if (i % 10 == 0){ 
     //This is still weird 
     //You shouldn't do this. Just throw the ArgumentException. 
     throw new AggregateException(new ArgumentException("I hate multiples of 10")); 
    } 
    return i % 2 == 0; 
} 

我覺得這個故事的寓意是不扔AggregateException除非你真的知道你在做什麼,特別是如果你已經在某種平行或基於某種操作的情況下。

+0

請編輯,以便您具有適當版本的代碼(拋出ArgumentException而不是拋出AggregateException)。你最終確實說了,但我擔心那些會看一眼並認爲發佈的代碼是正確的(不是)的人。 –

+1

引用了你不應該這樣做的評論。 –

7

我同意其他人:這是在.Net中的錯誤,你應該report it

原因在於內部類QueryTaskGroupState中的方法QueryEnd()。 它的反編譯(並略作修改爲清楚起見)代碼如下所示:

try 
{ 
    this.m_rootTask.Wait(); 
} 
catch (AggregateException ex) 
{ 
    AggregateException aggregateException = ex.Flatten(); 
    bool cacellation = true; 
    for (int i = 0; i < aggregateException.InnerExceptions.Count; ++i) 
    { 
    var canceledException = 
     aggregateException.InnerExceptions[i] as OperationCanceledException; 
    if (IsCancellation(canceledException)) 
    { 
     cacellation = false; 
     break; 
    } 
    } 
    if (!cacellation) 
    throw aggregateException; 
} 
finally 
{ 
    this.m_rootTask.Dispose(); 
} 
if (!this.m_cancellationState.MergedCancellationToken.IsCancellationRequested) 
    return; 
if (!this.m_cancellationState.TopLevelDisposedFlag.Value) 
    CancellationState.ThrowWithStandardMessageIfCanceled(
    this.m_cancellationState.ExternalCancellationToken); 
if (!userInitiatedDispose) 
    throw new ObjectDisposedException(
    "enumerator", "The query enumerator has been disposed."); 

基本上,這樣做是:

  • 重新拋出扁平AggregateException如果它包含任何非消除異常
  • 拋出新的取消異常,如果取消請求(或沒有投擲返回,我不真正理解這一部分,但我不認爲它在這裏相關)
  • 否則拋出ObjectDisposedException出於某種原因(假設userInitiatedDisposefalse,它是)

所以,如果你把沒有內部異常的AggregateExceptionex將包含您的空AggregateExcaptionAggregateException。調用Flatten()會將它變成空的AggreateException,這意味着它不包含任何非取消異常,所以代碼的第一部分認爲這是取消並且不會拋出。

但代碼的第二部分意識到這不是取消,所以它拋出一個完全假的異常。