TPL中Task执行的内联性线程重入
在没有 TPL(Task Parallel Library)之前,使用 ThreadPool 处理多线程事务及等待,可能类似如下代码:
1 class Program 2 { 3 [ThreadStatic] 4 static int PerThreadValue; 5 6 static void Main(string[] args) 7 { 8 Console.WriteLine("Main thread: {0}", 9 Thread.CurrentThread.ManagedThreadId); 10 11 Console.WriteLine(); 12 13 for (int i = 1; i <= 5; i++) 14 { 15 AutoResetEvent signalOuter = new AutoResetEvent(false); 16 ThreadPool.QueueUserWorkItem((s) => 17 { 18 PerThreadValue = i; 19 Console.WriteLine("Launch thread: {0}, Value: {1}", 20 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 21 22 AutoResetEvent signalInner = new AutoResetEvent(false); 23 ThreadPool.QueueUserWorkItem((n) => 24 { 25 Console.WriteLine(" Nested thread: {0}, Value: {1}", 26 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 27 signalInner.Set(); 28 }); 29 signalInner.WaitOne(); 30 31 Console.WriteLine(" Launch back thread: {0}, Value: {1}", 32 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 33 signalOuter.Set(); 34 }); 35 signalOuter.WaitOne(); 36 } 37 38 Console.ReadKey(); 39 } 40 }
ThreadPool 会为每个应用程序域维护 FIFO 的先入先出队列,每当调用 QueueUserWorkItem 时,线程池会将给定的任务放入队列中,等到有下一个可用线程时,从队列中取出执行。
所以执行的解决过发现每个任务都执行在不同的线程上。
当 .NET Framework 4 提供 TPL 库之后,我们可以通过另一种写法来完成同样的任务。
1 class Program 2 { 3 [ThreadStatic] 4 static int PerThreadValue; 5 6 static void Main(string[] args) 7 { 8 Console.WriteLine("Main thread: {0}", 9 Thread.CurrentThread.ManagedThreadId); 10 11 Console.WriteLine(); 12 13 for (int i = 1; i <= 5; i++) 14 { 15 Task.Factory.StartNew( 16 () => 17 { 18 PerThreadValue = i; 19 Console.WriteLine("Launch thread: {0}, Value: {1}", 20 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 21 22 Task.Factory.StartNew( 23 () => 24 { 25 Console.WriteLine(" Nested thread: {0}, Value: {1}", 26 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 27 }).Wait(); 28 29 Console.WriteLine(" Launch back thread: {0}, Value: {1}", 30 Thread.CurrentThread.ManagedThreadId, PerThreadValue); 31 }).Wait(); 32 } 33 34 Console.ReadKey(); 35 } 36 }
通常,我们会看到的结果,嵌套的 Task 与外部调用及等待的 Task 使用了相同的线程池线程。
如果机器够快的话,基本上所有 Task 都在同一个线程上执行。
TPL 中使用 TaskScheduler 来调度 Task的 执行,而 TaskScheduler 有一个特性名为 “Task Inlining”。
当外部 ThreadPool 线程正在阻塞并等待嵌套的 NestedTas k完成时,NestedTask 有可能在该等待的线程上执行。
这样做的优点是可以提高性能,因为节省并重用了被阻塞的线程。
在使用可能碰到的问题:
如果使用 ThreadStatic 标记某变量,则使该变量为每线程 TLS 独立存储,同时也意图该变量始终在同一线程中共享。
但在所示例子中,如果在父 Task 及执行线程中指定了变量的值,而子 Task 及相同执行线程则使用了相同的变量值, 则在某种需求下会产生问题。