C# Task详解(学习)
原文地址:C# Task详解 - 五维思考 - 博客园 (cnblogs.com)
1. Task 的优势
ThreadPool 相对于 Thread 具备许多优势,但 ThreadPool 却又存在一些使用上的不方便。
比如:
ThreadPool 不支持线程的取消、完成、失败通知等交互性操作。
ThreadPool 不支持线程执行的先后次序。
以前,开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。Task 在线程池的基础上进行了优化,并提供了更多的API。在FCL4.0中,编写多线程程序,Task显然已经优于传统的方式。
简单的Demo:
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace TaskDemo 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Task t = new Task(() => 12 { 13 Console.WriteLine($"{DateTime.Now} 任务开始工作..."); 14 // 模拟工作过程 15 Thread.Sleep(5000); 16 }); 17 t.Start(); 18 t.ContinueWith((task) => 19 { 20 Console.WriteLine($"{DateTime.Now} 任务完成,完成时的状态:"); 21 Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted); 22 }); 23 Console.ReadKey(); 24 } 25 } 26 }
2. Task 用法
2.1 创建任务
2.1.1 无返回值的方式
方式1:
1 var t1 = new Task(()=>TaskMethod("Task 1")); 2 t1.Start(); 3 Task.WaitAll(t1); // 等待所有任务结束 4 // 注:任务的状态 5 // Start 之前为Created 6 // Start 之后为 WaitingToRun
方式2:
1 Task.Run(()=>TaskMethod("Task 2"));
方式3:
1 Task.Factory.StartNew(()=>TaskMethod("Task 3")); // 直接异步的方法 2 // 或者 3 var t3 = Task.Factory.StartNew(()=>TaskMethod("Task 3")); 4 Task.WaitAll(t3); // 等待所有任务结束 5 // 任务的状态: 6 // Start 之前:Running 7 // Start 之后:Running
示例:
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace TaskDemo 6 { 7 class Program 8 { 9 10 static void Main(string[] args) 11 { 12 13 14 #region Demo2 15 var t1 = new Task(()=> TaskMethod("Task 1")); 16 var t2 = new Task(()=> TaskMethod("Task 2")); 17 t2.Start(); 18 t1.Start(); 19 Task.WaitAll(t1,t2); 20 //Task.Run(()=>TaskMethod("Task 3")); // 提示不包含该方法定义 21 Task.Factory.StartNew(()=>TaskMethod("Task 4")); 22 // 标记为长时间运行任务,则任务不会使用线程池,而在单独的线程中运行 23 Task.Factory.StartNew(()=>TaskMethod("Task 5"), TaskCreationOptions.LongRunning); 24 25 #region 常规使用方式 26 Console.WriteLine("主线程执行业务处理。"); 27 // 创建任务 28 Task task = new Task(()=> 29 { 30 Console.WriteLine("使用 System.Threading.Tasks.Task 执行异步操作。"); 31 for (int i = 0; i < 10; i++) 32 { 33 Console.WriteLine(i); 34 } 35 }); 36 // 启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) 37 task.Start(); 38 Console.WriteLine("主线程执行其他处理。"); 39 task.Wait(); 40 #endregion 41 42 Thread.Sleep(TimeSpan.FromSeconds(1)); 43 Console.ReadLine(); 44 #endregion 45 } 46 47 static void TaskMethod(string name) 48 { 49 Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", 50 name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); 51 } 52 } 53 }
两次运行不同的执行结果:
asyn/await 的实现方式:
1 using System; 2 using System.Threading.Tasks; 3 4 namespace AsyncAwaitDemo 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine("主线程执行业务处理。"); 11 AsyncFunction(); 12 Console.WriteLine("主线程执行其他处理。"); 13 for (int i = 0; i < 10; i++) 14 { 15 Console.WriteLine(string.Format("Main: i = {0}", i)); 16 } 17 Console.ReadLine(); 18 } 19 20 async static void AsyncFunction() 21 { 22 await Task.Delay(1); 23 Console.WriteLine("使用 System.Threading.Tasks.Task 执行异步操作。"); 24 for (int i = 0; i < 10; i++) 25 { 26 Console.WriteLine(string.Format("AsyncFunction: i = {0}", i)); 27 } 28 } 29 } 30 }
执行结果:
将 await Task.Delay(1); 移动到 AsyncFunction 方法的最后,执行结果:
(二)带返回值的方式
方式4:
1 Task<int> task = CreateTask("Task 1"); 2 task.Start(); 3 int result = task.Result;
示例:
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace TaskDemo 6 { 7 class Program 8 { 9 10 static void Main(string[] args) 11 { #region Demo3 12 TaskMethod("Main Thread Task"); 13 14 Task<int> task = CreateTask("Task 1"); 15 task.Start(); 16 int result = task.Result; 17 Console.WriteLine("Task 1 Result is : {0}", result); 18 19 task = CreateTask("Task 2"); 20 // 该任务会运行在主线程中 21 task.RunSynchronously(); 22 result = task.Result; 23 Console.WriteLine("Task 2 Result is : {0}", result); 24 25 task = CreateTask("Task 3"); 26 Console.WriteLine(task.Status); 27 task.Start(); 28 29 while (!task.IsCompleted) 30 { 31 Console.WriteLine(task.Status); 32 Thread.Sleep(TimeSpan.FromSeconds(0.5)); 33 } 34 35 Console.WriteLine(task.Status); 36 result = task.Result; 37 Console.WriteLine("Task 3 Result is : {0}", result); 38 39 #region 常规使用方式 40 // 创建任务 41 Task<int> getsumtask = new Task<int>(()=>GetSum()); 42 // 启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) 43 getsumtask.Start(); 44 Console.WriteLine("主线程执行其他处理"); 45 // 等待任务的完成执行过程。 46 getsumtask.Wait(); 47 // 获得任务的执行结果 48 Console.WriteLine("任务执行结果: {0}", getsumtask.Result.ToString()); 49 #endregion 50 51 Console.ReadLine(); 52 #endregion 53 } 54 55 static Task<int> CreateTask(string name) 56 { 57 return new Task<int>(() => TaskMethod(name)); 58 } 59 60 static int TaskMethod(string name) 61 { 62 Console.WriteLine("["+DateTime.Now +"]Task {0} is running on a thread id {1}. Is thread pool thread: {2}", 63 name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); 64 Thread.Sleep(TimeSpan.FromSeconds(2)); 65 return 42; 66 } 67 68 static int GetSum() 69 { 70 int sum = 0; 71 Console.WriteLine("使用 Task 执行异步操作。"); 72 for (int i = 0; i < 100; i++) 73 { 74 sum += i; 75 } 76 return sum; 77 } 78 79 } 80 }
执行结果:
async / await 的实现
1 using System; 2 using System.Threading.Tasks; 3 4 namespace TaskDemo 5 { 6 class Program 7 { 8 public static void Main() 9 { 10 var ret1 = AsyncGetsum(); 11 Console.WriteLine($"[{DateTime.Now}]主线程执行其他处理。"); 12 for (int i = 1; i <= 3; i++) 13 Console.WriteLine($"[{DateTime.Now}]Call Main()"); 14 int result = ret1.Result; //阻塞主线程 15 Console.WriteLine("["+DateTime.Now+"]任务执行结果:{0}", result); 16 } 17 18 async static Task<int> AsyncGetsum() 19 { 20 await Task.Delay(1); 21 int sum = 0; 22 Console.WriteLine($"[{DateTime.Now}]使用 Task 执行异步操作"); 23 for (int i = 0; i < 100; i++) 24 { 25 sum += i; 26 } 27 return sum; 28 } 29 } 30 }
执行结果:
2.2 组合任务 ContinueWith
简单Demo:
1 using System; 2 using System.Threading.Tasks; 3 4 namespace ContiuneWithDemo 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 #region Demo1 11 // 创建一个任务 12 Task<int> task = new Task<int>(() => 13 { 14 int sum = 0; 15 Console.WriteLine("使用 Task 执行异步操作。"); 16 for (int i = 0; i < 100; i++) 17 { 18 sum += i; 19 } 20 return sum; 21 }); 22 // 启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) 23 task.Start(); 24 // 任务完成时执行处理。 25 Task cwt = task.ContinueWith(t => 26 { 27 Console.WriteLine("任务完成后的执行结果:{0}", t.Result.ToString()); 28 }); 29 task.Wait(); 30 cwt.Wait(); 31 Console.ReadKey(); 32 #endregion 33 } 34 } 35 }
执行结果:
任务的串行:
1 ConcurrentStack<int> stack = new ConcurrentStack<int>(); 2 // t1 先串行 3 var t1 = Task.Factory.StartNew(() => 4 { 5 stack.Push(1); 6 stack.Push(2); 7 }); 8 // t2,t3 并行执行 9 var t2 = t1.ContinueWith(t => 10 { 11 int result; 12 stack.TryPop(out result); 13 Console.WriteLine("Task t2 result={0}, Thread id {1}", result, Thread.CurrentThread.ManagedThreadId); 14 }); 15 // t2,t3 并行执行 16 var t3 = t1.ContinueWith(t => 17 { 18 int result; 19 stack.TryPop(out result); 20 Console.WriteLine("Task t3 result={0}, Thread id {1}", result, Thread.CurrentThread.ManagedThreadId); 21 }); 22 // 等待 t2 和 t3 执行完 23 Task.WaitAll(t2, t3); 24 // t4 串行执行 25 var t4 = Task.Factory.StartNew(() => 26 { 27 Console.WriteLine("当前集合元素个数:{0}, Thread id {1}", stack.Count, Thread.CurrentThread.ManagedThreadId); 28 }); 29 t4.Wait(); 30 31 Console.ReadKey();
执行结果:
子任务:
1 using System; 2 using System.Threading.Tasks; 3 4 namespace ContiuneWithDemo 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Task<string[]> parent = new Task<string[]>(state => 11 { 12 Console.WriteLine(state); 13 string[] result = new string[2]; 14 // 创建并启动子任务 15 new Task(() => { result[0] = "我是子任务1。"; }, TaskCreationOptions.AttachedToParent).Start(); 16 new Task(() => { result[1] = "我是子任务2。"; }, TaskCreationOptions.AttachedToParent).Start(); 17 return result; 18 }, "我是父任务,并在我的处理过程中创建多个子任务,所有子任务完成以后我才会结束执行。"); 19 // 任务处理完成后执行的操作 20 parent.ContinueWith(t => { Array.ForEach(t.Result, r => Console.WriteLine(r)); }); 21 // 启动父任务 22 parent.Start(); 23 // 等待任务结束 Wait 只能等待父线程结束,没办法等到父线程的 ContinueWith 结束 24 parent.Wait(); 25 Console.ReadLine(); 26 } 27 } 28 }
执行结果:
动态并行(TaskCreationOptions.AttachedToParent)父任务等待所有子任务完成后,整个任务才算完成
1 using System; 2 using System.Collections.Concurrent; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace ContiuneWithDemo 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 Node root = GetNode(); 13 DisplayTree(root); 14 Console.ReadKey(); 15 } 16 17 class Node 18 { 19 public Node Left { get; set; } 20 public Node Right { get; set; } 21 public string Text { get; set; } 22 } 23 24 static Node GetNode() 25 { 26 Node root = new Node 27 { 28 Left = new Node 29 { 30 Left = new Node 31 { 32 Text = "L-L" 33 }, 34 Right = new Node 35 { 36 Text = "L-R" 37 }, 38 Text = "L" 39 }, 40 Right = new Node 41 { 42 Left = new Node 43 { 44 Text = "R-L" 45 }, 46 Right = new Node 47 { 48 Text = "R-R" 49 }, 50 Text = "R" 51 }, 52 Text = "Root" 53 }; 54 return root; 55 } 56 57 static void DisplayTree(Node root) 58 { 59 var task = Task.Factory.StartNew(()=>DisplayNode(root), 60 CancellationToken.None, 61 TaskCreationOptions.None, 62 TaskScheduler.Default); 63 task.Wait(); 64 } 65 66 static void DisplayNode(Node current) 67 { 68 if (current.Left != null) 69 { 70 Task.Factory.StartNew(()=>DisplayNode(current.Left), 71 CancellationToken.None, 72 TaskCreationOptions.AttachedToParent, 73 TaskScheduler.Default); 74 } 75 if (current.Right != null) 76 { 77 Task.Factory.StartNew(()=>DisplayNode(current.Right), 78 CancellationToken.None, 79 TaskCreationOptions.AttachedToParent, 80 TaskScheduler.Default); 81 } 82 Console.WriteLine("当前节点的值为{0};处理的 ThreadId = {1}", current.Text, Thread.CurrentThread.ManagedThreadId); 83 } 84 } 85 }
执行结果:
2.3 取消任务 CancellationTokenSource
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace CanellationTokenSourceDemo 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 var cts = new CancellationTokenSource(); 12 var longTask = new Task<int>(()=>TaskMethod("Task 1", 10, cts.Token), cts.Token); 13 Console.WriteLine(longTask.Status); 14 cts.Cancel(); 15 Console.WriteLine(longTask.Status); 16 Console.WriteLine("First task has been canelled before execution."); 17 cts = new CancellationTokenSource(); 18 longTask = new Task<int>(()=>TaskMethod("Task 2", 10, cts.Token), cts.Token); 19 longTask.Start(); 20 for (int i = 0; i < 5; i++) 21 { 22 Thread.Sleep(TimeSpan.FromSeconds(0.5)); 23 Console.WriteLine(longTask.Status); 24 } 25 cts.Cancel(); 26 for (int i = 0; i < 5; i++) 27 { 28 Thread.Sleep(TimeSpan.FromSeconds(0.5)); 29 Console.WriteLine(longTask.Status); 30 } 31 Console.WriteLine("A task has been completed with result {0}.", longTask.Result); 32 Console.ReadKey(); 33 } 34 35 private static int TaskMethod(string name, int seconds, CancellationToken token) 36 { 37 Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", 38 name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); 39 for (int i = 0; i < seconds; i++) 40 { 41 Thread.Sleep(TimeSpan.FromSeconds(1)); 42 if (token.IsCancellationRequested) 43 { 44 return -1; 45 } 46 } 47 return 42 * seconds; 48 } 49 } 50 }