第4章:使用任务并行库
4.1 简介
使用线程池可以使我们在减少并行度花销是节省操作系统资源。我们可以认为线程池是一个抽象层,其向程序员隐藏了使用线程的细节,使我们专心处理程序逻辑,而不是各种线程问题。
任务并行库(Task Parallel Library,简称TPL),可被认为是线程池之上的有一个抽象层,对其程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度的API。
TPL的核心概念是任务。一个任务代表了一个异步操作,该操作可以通过多种方式运行,可以使用或不使用独立线程运行。
4.2 创建任务
任务1-4是放到线程池里的,所以任务的执行顺序是不确定的
static void Main(string[] args) { var t1 = new Task(() => TaskMethod("Task 1")); var t2 = new Task(() => TaskMethod("Task 2")); t2.Start(); t1.Start(); // 任务 Task.Run(() => TaskMethod("Task 3")); Task.Factory.StartNew(() => TaskMethod("Task 4")); Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.Read(); } static void TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); }
4.3 使用任务执行基本的操作
任务1的task.Result会阻塞主线程,直到子线程完成。
任务2是同步任务,在主线程完成。
任务3没有阻塞主线程,只是在主线程循环打印任务状态。
static void Main(string[] args) { TaskMethod("Main Thread Task"); Task<int> task = CreateTask("Task 1"); task.Start(); int result = task.Result; Console.WriteLine("Result is:{0}", result); task = CreateTask("Task 2"); task.RunSynchronously(); result = task.Result; Console.WriteLine("Result is:{0}", result); task = CreateTask("Task 3"); task.Start(); while (!task.IsCompleted) { Console.WriteLine(task.Status); Thread.Sleep(TimeSpan.FromSeconds(0.5)); } Console.WriteLine(task.Status); result = task.Result; Console.WriteLine("Result is:{0}", result); Console.Read(); } static Task<int> CreateTask(string name) { return new Task<int>(() => TaskMethod(name)); } static int TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(2000); return 42; }
4.4 组合任务
创建一个任务,使其在父任务完成后才会被运行,另外将探寻为非常短暂的任务节省线程开销的可能性。
-- OnlyOnRanToCompletion:只有在firstTask完成之后执行
static int TaskMethod(string name, int seconds) { Console.WriteLine($"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}. " + $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); return seconds * 42; }
var firstTask = new Task<int>(() => TaskMethod("First Task", 3)); // OnlyOnRanToCompletion:只有在firstTask完成之后执行 firstTask.ContinueWith(t => Console.WriteLine($"The first answer is {t.Result}.Thread id {Thread.CurrentThread.ManagedThreadId}," + $"is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}"), TaskContinuationOptions.OnlyOnRanToCompletion); firstTask.Start();
-- TaskContinuationOptions.ExecuteSynchronously:如果没有Thread.Sleep(4),代码会在线程池中执行,因为之前的任务还没有执行完成。如果有Thread.Sleep(4),前面的任务已经执行完,所以会在主程序中执行,不会放到线程池里面来切换线程,这样更快。
var secondTask = new Task<int>(() => TaskMethod("Second Task", 2)); secondTask.Start(); Thread.Sleep(TimeSpan.FromSeconds(4)); Task continuation = secondTask.ContinueWith(t => Console.WriteLine($"The second answer is {t.Result}." + $"Thread id {Thread.CurrentThread.ManagedThreadId}, is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}"), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); continuation.GetAwaiter().OnCompleted(() => Console.WriteLine($"Continuation Task Completed! Thread is {Thread.CurrentThread.ManagedThreadId}," + $"is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}")); Thread.Sleep(TimeSpan.FromSeconds(2));
-- TaskCreationOptions.AttachedToParent
// 外部任务 var firstTask = new Task<int>(() => { // 子任务 var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task", 5), TaskCreationOptions.AttachedToParent); innerTask.ContinueWith(t => TaskMethod("Third Task", 2), TaskContinuationOptions.AttachedToParent); return TaskMethod("First Task", 2); }); firstTask.Start();
4.7、实现取消选项 CancellationTokenSource.Cancel
static int TaskMethod(string name, int seconds,CancellationToken token) { Console.WriteLine($"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." + $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}"); for (int i = 0; i < seconds; i++) { Thread.Sleep(TimeSpan.FromSeconds(1)); if (token.IsCancellationRequested) { return -1; } } return 42 * seconds; }
-- 任务开始之前取消
var cts = new CancellationTokenSource(); var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token); Console.WriteLine(longTask.Status); cts.Cancel(); Console.WriteLine(longTask.Status); Console.WriteLine("First task has been cancelled before execution");
-- 任务启动后取消
static void Main(string[] args) { var cts = new CancellationTokenSource(); var task = new Task<int>(() => TaskMethod("Task1", 10, cts.Token), cts.Token); task.Start(); for (int i = 0; i < 5; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(task.Status); } cts.Cancel(); for (int i = 0; i < 5; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(task.Status); } Console.Read(); }
4.8、处理任务中的异常
static int TaskMethod(string name, int seconds) { Console.WriteLine($"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." + $"Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); throw new Exception("Boom!"); }
-- task.Result
static void Main(string[] args) { Task<int> task; try { task = Task.Run(() => TaskMethod("Task1", 2)); var result = task.Result; Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex); } Console.Read(); }
-- task.GetAwaiter().GetResult()
static void Main(string[] args) { Task<int> task; try { task = Task.Run(() => TaskMethod("Task2", 2)); var result = task.GetAwaiter().GetResult(); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex); } Console.WriteLine("123"); Console.Read(); }
-- 捕获异常,没有try catch
static void Main(string[] args) { var t1 = new Task<int>(() => TaskMethod("Task 3", 3)); var t2 = new Task<int>(() => TaskMethod("Task 4", 2)); var complexTask = Task.WhenAll(t1, t2); var exceptionHandler = complexTask.ContinueWith(t => Console.WriteLine($"Exception caught {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); t1.Start(); t2.Start(); Console.Read(); }
4.9、并行运行任务
static int TaskMethod(string name, int seconds) { Console.WriteLine($"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." + $"Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}"); Thread.Sleep(TimeSpan.FromSeconds(seconds)); return 42 * seconds; }
-- Task.WhenAll
static void Main(string[] args) { var t1 = new Task<int>(() => TaskMethod("Task 1", 3)); var t2 = new Task<int>(() => TaskMethod("Task 2", 2)); var complexTask = Task.WhenAll(t1, t2); var exceptionHandler = complexTask.ContinueWith(t => Console.WriteLine($"The first answer is {t.Result[0]}, the second answer is {t.Result[1]}"), TaskContinuationOptions.OnlyOnRanToCompletion); t1.Start(); t2.Start(); Console.Read(); }
-- Task.WhenAny
static void Main(string[] args) { var tasks = new List<Task<int>>(); for (int i = 0; i < 4; i++) { var counter = i; var task = new Task<int>(() => TaskMethod($"Task {counter}", counter)); tasks.Add(task); task.Start(); } while (tasks.Count > 0) { var completedTask = Task.WhenAny(tasks).Result; tasks.Remove(completedTask); Console.WriteLine($"A task has been completed with result {completedTask.Result}"); } Console.Read(); }
4.10、任务调度,UI交互