[PLINQ]AggragateException

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呢? 原因有兩個

    1. 第5個null可能尚未排入任務。

    2. 或者兩個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);
}

輸出:

從結果看來有兩個重點

  1. AggregateException完全沒有收集到半個InnerException,因為Exception都在委派裡面被catch

  2. 因為沒有觸發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

https://books.google.com.tw/books?id=DqNCAwAAQBAJ&pg=PT780&lpg=PT780&dq=PLINQ+AggregateException&source=bl&ots=VdG_iNmhwv&sig=aAZ1IbZCT3cGo-FgqQHhpmBMCsQ&hl=zh-TW&sa=X&ved=0ahUKEwjs9OeQmazKAhVKn5QKHZSNBssQ6AEIVDAH#v=onepage&q=PLINQ%20AggregateException&f=false

 

 

一天一分享,身體好健康。

該追究的不是過去的原因,而是現在的目的。