AggragateException
使用PLINQ查詢過程中拋出Exception,PLINQ並不會立刻停止所有的查詢。 PLINQ查詢通常是多執行緒處理,當其中一個執行緒拋出Exception時,系統會嘗試停掉所有正在執行查詢的執行緒。 與此同時其他執行緒也許會一起拋出Exception。 在結束的時候,也許會有一個以上的Exception被拋出。 因為這個原因,PLINQ內部所有拋出的Exception在查詢被停止的時候,會被收集在單一個AggregateException instance裡面,而AggregateException的InnerExceptions property包含所有PLINQ拋出來的Exception。
以底下例子為例
*註:此例子,因PLINQ使用striping strategy的關係,只要是雙核心的CPU,一個核心會負責處理index是奇數的元素,一個核心會處理index是偶數的元素
static void Main(string[] args)
{
var list = new List<int>() { 1, 2, 3, 4, 5 };
try
{
var data = list.AsParallel().Select(Test).ToList();
}
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
private static bool Test(int n)
{
if (n == 1)
{
Thread.Sleep(1000);
Console.WriteLine("element : {0}", n);
}
if (n == 3)
{
Thread.Sleep(3000);
Console.WriteLine("element : {0}", n);
}
if (n == 5)
{
Thread.Sleep(5000);
Console.WriteLine("element : {0}", n);
}
if (n == 2)
{
Thread.Sleep(5000);
Console.WriteLine("element : {0}", n);
}
if (n == 4)
{
Console.WriteLine("element : {0}", n);
throw new Exception("New exception");
}
return true;
}
輸出:
可以看到結果,當其中一條執行緒拋出Exception的時候,還是會等到其他執行緒把已排定的工作完成,且PLINQ也不會再向來源取資料,最後拋出AggregateException結束整個查詢。
既然知道PLINQ怎麼拋出AggregateException後,我在這邊再補充一個實際的例子。
我現在有一個長度為271的List<string>,其中有五個元素我給null,然後我會對字串使用Substring處理,所以遇到null會拋出Exception的案例,我們就來看看程式會處理幾筆資料,AggregateException到底會取得幾個InnerException。
int count;
private object _lock = new object();
public void Run()
{
//new List<string>(){....null,....null,...nul};在271個字串,我埋了5個null
List<string> data = this.GetData();
Console.WriteLine("原本的資料有{0}筆 ", data.Count);
try
{
data.AsParallel().ForAll(x =>
{
lock (_lock)
{
count++;
}
var result = x.Substring(2, 1); // 遇到null會出現exception
});
}
catch (AggregateException ae)
{
int i = 1;
foreach (var ex in ae.InnerExceptions)
{
i++;
}
Console.WriteLine("總共有{0}個InnerException", i);
}
Console.WriteLine("被處理的數量有{0}個", count);
}
輸出:
從結果來看有三個重點
-
當拋出第一個Exception,PLINQ不會再向來源要資料處理,並且會嘗試停止所有執行緒查詢,且會讓該執行緒完成已排定的工作,所以被處理的數量只有137個。
-
當拋出第一個Exception時,其他執行緒完成當下任務的同時,如果又拋出Exception,則會交由AggregateException收集起來,收集了4個。
-
我埋了5個null,為什麼只有4個InnerException呢? 原因有兩個
-
第5個null可能尚未排入任務。
-
或者兩個null放在同一個執行緒底下,所以只觸發一次。
-
如果拋出Exception還想要讓查詢繼續的話,我們可以再委派裡面直接catch Exception,這樣AggregateException就不會被拋出來,查詢會繼續下去。
private int count;
private int count2;
private object _lock = new object();
public void Run()
{
//new List<string>(){....null,....null,...nul};在271個字串,我埋了5個null
List<string> data = this.GetData();
Console.WriteLine("原本的資料有{0}筆 ", data.Count);
try
{
data.AsParallel().ForAll(x =>
{
try
{
lock (_lock)
{
count++;
}
var result = x.Substring(2, 1); // 遇到null會出現exception
}
catch (Exception e)
{
lock (_lock)
{
count2++;
}
}
});
}
catch (AggregateException ae)
{
int i = 1;
foreach (var ex in ae.InnerExceptions)
{
i++;
}
Console.WriteLine("總共有{0}個InnerException", i);
}
Console.WriteLine("委派自行catct Exception有{0}個", count2);
Console.WriteLine("被處理的數量有{0}個", count);
}
輸出:
從結果看來有兩個重點
-
AggregateException完全沒有收集到半個InnerException,因為Exception都在委派裡面被catch
-
因為沒有觸發AggregateException,所以PLINQ會持續向來源要資料,直到把資料處理完,這就是被處理數量有271個的原因
參考:
http://stackoverflow.com/questions/32430134/plinq-exception
https://msdn.microsoft.com/en-us/library/dd460712(v=vs.110).aspx
http://www.albahari.com/threading/part5.aspx#_Working_with_AggregateException
一天一分享,身體好健康。
該追究的不是過去的原因,而是現在的目的。