多线程 Task
Net Framework4.0引入了一个新的关于异步操作的API,它叫做.任务并行库( Task Parallel Library,简称TPL), .Net Framework 4.5版对该API进行了轻微的改进,使用更简单。TPL可被认为是线程池之上的又一个抽象层,其对程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度的APL, TPL的核心概念是任务。一个任务代表了一个异步操作,该操作可以通过多种方式运行,可以使用或不使用独立线程运行。
一个任务可以通过多种方式和其他任务组合起来。例如,可以同时启动多个任务,等待所有任务完成,然后运行一个任务对之前所有任务的结果进行一些计算。TPL与之前的模式相比,其中一个关键优势是其具有用于组合任务的便利的API,
处理任务中的异常结果有多种方式。由于一个任务可能会由多个其他任务组成,这些任,务也可能依次拥有各自的子任务,所以有一个AggregateException的概念。这种异常可以捕获底层任务内部的所有异常,并允许单独处理这些异常。
而且,最后但并不是最不重要的, C# 5.0已经内置了对TPL的支持,允许我们使用新的 await和async关键字以平滑的、舒服的方式操作任务。
创建任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | namespace ConsoleAppTest { internal class Program { static void Main( string [] args) { var t1= new Task(()=> TaskMethod( "Task 1" )); //仅当调用Start()方法时才会执行 var t2 = new Task(() => TaskMethod( "Task 2" )); t2.Start(); t1.Start(); Task.Run(() => TaskMethod( "Task 3" )); //立即执行,是StartNew 的快捷方式 Task.Factory.StartNew(() => TaskMethod( "Task 4" )); //立即执行 //标记任务长时间运行,结果任务将不会使用线程池,而在单独的线程中运行, //然而,根据运行该任务的当前的任务调度程序( task scheduler)运行方式有可能不同。 Task.Factory.StartNew(()=>TaskMethod( "Task 5" ),TaskCreationOptions.LongRunning); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.ReadKey(); } static void TaskMethod( string name) { Console.WriteLine( "任务: {0} 线程ID: {1}. 是否在线程池中: {2}" , name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } } } |
注意:每次运行,任务的执行顺序可能不一样,Task 5 标记任务长时间运行,结果任务将不会使用线程池,而在单独的线程中运行,
使用任务执行基本操作
获取任务的结果
1 | int result = task.Result; //得到任务执行后的结果 |
同步运行
1 | task.RunSynchronously(); //同步运行 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | ///<summary> /// 使用任务执行基本的操作 /// </summary> internal class TaskRunBasicOperate { public static void StartTask() { 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" ); // 该任务会运行在主线程中,该任务的输出与直接调用TaskMethod的输出完全一样。 // 这是个非常好的优化,可以避免使用线程池来执行非常短暂的操作。 task.RunSynchronously(); //同步运行 result = task.Result; Console.WriteLine( "Result is: {0}" , result); //没有调用 task.Result 所有不会阻塞主线程 task = CreateTask( "Task 3" ); Console.WriteLine(task.Status); 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); } /// <summary> /// 返回一个整数的任务 /// </summary> /// <param name="name"></param> /// <returns></returns> 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(TimeSpan.FromSeconds(2)); return 42; } } } |
组合任务
设置相互依赖的任务。我们将学习如何创建一个任务,使其在父任务完成后才会被运行。另外,将探寻为非常短暂的任务节省线程开销的可能性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | namespace ConsoleAppTest.TaskTest { /// <summary> /// 组合任务 /// </summary> internal class AssembleTask { public static void StartTask() { var firstTask = new Task< int >(() => TaskMethod( "First Task" , 3)); var secondTask = new Task< int >(() => TaskMethod( "Second Task" , 2)); //当前任务完成后的,后续操作,参数为前一个任务返回的结果 firstTask.ContinueWith( t => Console.WriteLine( "The first answer is {0}. Thread id {1}, is thread pool thread: {2}" , t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread), TaskContinuationOptions.OnlyOnRanToCompletion); firstTask.Start(); secondTask.Start(); //等待上面两个任务运行完成 Thread.Sleep(TimeSpan.FromSeconds(4)); //同步执行,在主线程中运行 Task continuation = secondTask.ContinueWith( t => Console.WriteLine( "The second answer is {0}. Thread id {1}, is thread pool thread: {2}" , t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); //后后续操作,这些方法是C# 5.0语言中异步机制中的方法。 continuation.GetAwaiter().OnCompleted( () => Console.WriteLine( "Continuation Task Completed! Thread id {0}, is thread pool thread: {1}" , Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine(); //带子任务的任务,仅当所有子任务结束工作,父任务才会完成。 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(); while (!firstTask.IsCompleted) { Console.WriteLine(firstTask.Status); Thread.Sleep(TimeSpan.FromSeconds(0.5)); } Console.WriteLine(firstTask.Status); Thread.Sleep(TimeSpan.FromSeconds(10)); } static int TaskMethod( string name, int seconds) { 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(TimeSpan.FromSeconds(seconds)); return 42 * seconds; } } } |
异步操作实现取消流程
如何正确的使用取消标志,以及在任务真正运行前如何得知其是否被取消。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | internal class CancellationTokenTest { public static void StartTask() { 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" ); cts = new CancellationTokenSource(); longTask = new Task< int >(() => TaskMethod( "Task 2" , 10, cts.Token), cts.Token); longTask.Start(); for ( int i = 0; i < 5; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(longTask.Status); } cts.Cancel(); for ( int i = 0; i < 5; i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(longTask.Status); } Console.WriteLine( "A task has been completed with result {0}." , longTask.Result); } private static int TaskMethod( string name, int seconds, CancellationToken token) { Console.WriteLine( "Task {0} is running on a thread id {1}. Is thread pool thread: {2}" , name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); for ( int i = 0; i < seconds; i++) { Thread.Sleep(TimeSpan.FromSeconds(1)); if (token.IsCancellationRequested) return -1; } return 42 * seconds; } } |
处理任务中的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | internal class ExceptionTask { public static void StartTask() { Task< int > task; //try //{ // task = Task.Run(() => TaskMethod("Task 1", 2)); // int result = task.Result; // Console.WriteLine("Result: {0}", result); //} //catch (Exception ex) //{ // Console.WriteLine("Exception caught: {0}", ex); //} //Console.WriteLine("----------------------------------------------"); //Console.WriteLine(); //无需封装异常,因为TPL基础设施会提取该异常。 //如果只有一个底层任务,那么一次只能获取一个原始异常,这种设计非常合适。 try { task = Task.Run(() => TaskMethod( "Task 2" , 2)); int result = task.GetAwaiter().GetResult(); //推荐,仅当只有一个任务时 Console.WriteLine( "Result: {0}" , result); } catch (Exception ex) { Console.WriteLine( "Exception caught: {0}" , ex); } Console.WriteLine( "----------------------------------------------" ); Console.WriteLine(); // 使用后续操作来处理异常,只有之前,的任务完成前有异常时,该后续操作才会被执行 //结果打印出了AggregateException,其内部封装了两个任务抛出的异常。 //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: {0}", t.Exception), // TaskContinuationOptions.OnlyOnFaulted // ); //t1.Start(); //t2.Start(); Thread.Sleep(TimeSpan.FromSeconds(5)); } static int TaskMethod( string name, int seconds) { 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(TimeSpan.FromSeconds(seconds)); throw new Exception( "Boom!" ); return 42 * seconds; } } |
并行运行任务
同时运行多个异步任务。我们将学习当所有任务都完成或任意一个任务,完成了工作时,如何高效地得到通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | /// <summary> /// 并行运行任务 /// </summary> internal class MultiTask { public static void StartTask() { var firstTask = new Task< int >(() => TaskMethod( "First Task" , 3)); var secondTask = new Task< int >(() => TaskMethod( "Second Task" , 2)); var whenAllTask = Task.WhenAll(firstTask, secondTask); //该任务将会在所有任务完成后运行 //该任务的结果提供了一个结果数组,第一个元素是第.个任务的结果,第二个元素是第二个任务的结果,以此类推。 whenAllTask.ContinueWith(t => Console.WriteLine( "The first answer is {0}, the second is {1}" , t.Result[0], t.Result[1]), TaskContinuationOptions.OnlyOnRanToCompletion ); firstTask.Start(); secondTask.Start(); Thread.Sleep(TimeSpan.FromSeconds(4)); var tasks = new List<Task< int >>(); for ( int i = 1; i < 4; i++) { int counter = i; var task = new Task< int >(() => TaskMethod( string .Format( "Task {0}" , counter), counter)); tasks.Add(task); task.Start(); } //获取任务的完成进展情况或在运行任务时使用超时,都可以使用Task.WhenAny方法。 //例如,我们等待一组任务运行,并且使用其中一个任务用来记录是否超时。如果该任务先完成, //则只需取消掉其他还未完成的任务。 while (tasks.Count > 0) { //等待这些任务中的任何一个完成,当有一个完成任务后,从列表中移除该任务并继续等待其他任务完成 var completedTask = Task.WhenAny(tasks).Result; tasks.Remove(completedTask); Console.WriteLine( "A task has been completed with result {0}." , completedTask.Result); } Thread.Sleep(TimeSpan.FromSeconds(1)); } static int TaskMethod( string name, int seconds) { 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(TimeSpan.FromSeconds(seconds)); return 42 * seconds; } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章