1

爲什麼我得到以下文的代碼段不同的結果ThreadLocal的聚合和任務並行庫

代碼示例1

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Threading; 

namespace ThreadLocalTasks 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 

      ThreadLocal<int> aggregrations = new ThreadLocal<int>(); 
      Task<int>[] tasks = new Task<int>[10]; 

      for (int i = 0; i < tasks.Length; i++) 
      { 
       aggregrations.Value = 0; 
       int tempi = i; 
       tasks[tempi] = new Task<int>(() => 
       { 
        int temp = 0; 
        for (int j = 1; j <= 3; j++) 
        { 
         temp += j; 
        } 
        aggregrations.Value = temp; 
        return aggregrations.Value; 
       }); 

      } 

      tasks.ToList().ForEach(x => { 
       x.Start(); 
      }); 

      Task.WaitAll(tasks); 

      int sum = 0; 

      tasks.ToList().ForEach(x => 
      { 
       sum += x.Result; 
      }); 

      Console.WriteLine("Sum: {0}", sum); 

      Console.WriteLine("Press any key to quit.."); 
      Console.ReadKey(); 
     } 
    } 
} 

樣品2

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Threading; 

namespace ThreadLocalTasks 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 

      ThreadLocal<int> aggregrations = new ThreadLocal<int>(); 
      Task<int>[] tasks = new Task<int>[10]; 

      for (int i = 0; i < tasks.Length; i++) 
      { 
       aggregrations.Value = 0; 
       int tempi = i; 
       tasks[tempi] = new Task<int>(() => 
       { 
        for (int j = 1; j <= 3; j++) 
        { 
         aggregrations.Value += j; 
        } 
        return aggregrations.Value; 
       }); 

      } 

      tasks.ToList().ForEach(x => { 
       x.Start(); 
      }); 

      Task.WaitAll(tasks); 

      int sum = 0; 

      tasks.ToList().ForEach(x => 
      { 
       sum += x.Result; 
      }); 

      Console.WriteLine("Sum: {0}", sum); 

      Console.WriteLine("Press any key to quit.."); 
      Console.ReadKey(); 
     } 
    } 
} 

回答

1

爲什麼你連嘗試在這裏使用ThreadLocal存儲,而不是在任務中使用局部變量?任務並行庫可以很好地重用一個線程來執行多個任務,並且線程本地存儲將被覆蓋。在第一個例子也許能行得通,因爲你是不是每個線程被重用時進行復位,但這種效果會更好:

for (int i = 0; i < tasks.Length; i++) 
     { 
      tasks[i] = new Task<int>(() => 
      { 
       int sum = 0; 
       for (int j = 1; j <= 3; j++) 
       { 
        sum += j; 
       } 
       return sum; 
      }); 

     } 

解釋,你的代碼實際上做:

在你第一個例子,你初始化啓動線程在單個線程本地的值改爲0,但你多次這樣做(顯然不是你通過將初始化for循環預期 - 錯誤#1)。你積累在這是很好的一個任務局部變量但是你用覆蓋,即使是線程本地值可以在多個任務之間執行順序(每核例如一個線程)共享的結果線程局部值 - BUG#2。這會導致一些任務共享相同的線程本地值。錯誤#3:當你回到你幸運線程局部值,因爲這將是相同的溫度和其它線程所以它等同於只使用任務中的一個局部變量可以改變了它。

在第二個例子中,你就初始化同樣的錯誤。但你去到雙計數值,因爲線程本地值未在每個任務的開始復位,所以如果兩個任務在同一線程上運行的第一個可能返回1 + 2 + 3和第2可能會返回6 + 1 + 2 + 3。

+0

這是不是這麼多,爲什麼你會做到這一點,更多的是這個技術是正確的。 – Blair 2010-11-28 01:28:20

+0

我添加了一個更詳細的解釋,爲什麼你的兩個例子都是'不正確的'。 – 2010-12-01 01:12:12