接上文 多线程编程学习笔记——基础(一)
接上文 多线程编程学习笔记——基础(二)
接上文 多线程编程学习笔记——基础(三)
接上文 多线程编程学习笔记——线程同步(一)
接上文 多线程编程学习笔记——线程同步(二)
接上文 多线程编程学习笔记——线程同步(三)
接上文 多线程编程学习笔记——线程池(一)
接上文 多线程编程学习笔记——线程池(二)
接上文 多线程编程学习笔记——线程池(三)
前面我们学习了什么是线程,线程之间的同步,使用线程池。使用线程池可以减少我们大量短时间操作的并行线程所用的操作系统资源。
在net framework 4.0中微软又提供了一个新的异步操作的功能,叫做任务并行库(TPL)。任务并行库的核心是任务(task)。一个任务代表了一个异步操作,譔操作可以通过多种方式运行,可以使用或不使用独立的线程。
一个任务(Task)可以通过多种方式和其他任务组合起来使用。例如,可以同时开启多个任务,等待所有任务完成,再起一个任务进行操作。一个任务可以有多个其他任务组成,这些任务也可以依次拥有自己的子任务。
C#5.0及之后的版本都已经内置了对TPL的支持,允许我们使用await与async关键字进行任务执行。
以下示例,我们使用.Net Framework 4.5之后版本。
一、 创建任务
下面的示例,我们使用task构造函数创建了两个任务。我们传入了一个lambda表达式做为操作任务。然后使用start启动任务。
接着,我们使用task.Run和task.startNew方法来运行两个任务。与使用task构造函数不同之处,在于这两个被创建的任务会立即执行。所以无需显式地调用 这些任务的Start方法。从task1到task4所有任务都是放在线程池中执行的,多次执行,可以发现执行顺序是不一样的。
Task5,由于我们标记为了长时间运行,所以是一个单独的线程,不是线程池中的线程来运行的。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine("Task 运行示例 ————{0}",DateTime.Now); var task1 = new Task(() => TaskOper("Task1")); var task2 = new Task(() => TaskOper("Task2")); task1.Start(); task2.Start(); Task.Factory.StartNew(() => TaskOper("Task 3")); Task.Run(() => TaskOper("Task 4")); //长时间运行 Task.Factory.StartNew(() => TaskOper("Task 5"),TaskCreationOptions.LongRunning); Thread.Sleep(1000); Console.ReadKey(); } private static void TaskOper(string name) { Console.WriteLine("Task 运行在 线程 ID:{0} 上,这个线程是不是线程池中的线程:{1},名称: {2}", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread, name); } } }
2.运行结果如下图。我把程序运行了两次。请自行查看不同之处。
二、 使用任务执行基本的操作
本示例是从任务中获取结果值。我们通过不同的执行结果来显示在线程池中执行与在主线程中执行的不同之处。
1. 代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine("Task 基本操作 ————"); TaskOper("----主线程Task运行"); Task<string> task1 =CreateTask("Task1"); task1.Start(); string result = task1.Result; Console.WriteLine(" 运行结果——{0}", result); Task<string> task2 = CreateTask("Task2"); task2.RunSynchronously(); result = task1.Result; Console.WriteLine(" 运行结果——{0}", result); Task<string> task3 = CreateTask("Task3"); task3.Start(); while(!task3.IsCompleted) { Console.WriteLine(" 状态——{0}", task3.Status); Thread.Sleep(500); } Console.WriteLine(" ——状态—{0}", task3.Status); result = task3.Result; Console.WriteLine(" 运行结果——{0}", result); Console.ReadKey(); } private static string TaskOper(string name) { Console.WriteLine("Task 线程 ID:{0} 上,是不是线程池中的线程:{1},名称: {2}", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread, name); Thread.Sleep(2000); return string.Format("线程ID:{0},名称:{1}", Thread.CurrentThread.ManagedThreadId,name); } static Task<string> CreateTask(string name) { return new Task<string>(() => TaskOper(name)); } } }
2.程序运行结果如下图。
首先直接运行TaskOper方法,根据程序运行结果,我们可以知道这个方法是被同步执行的。
然后我们运行了task1,使用start方法启动任务并等待结果。这个任务会被放在线程池中运行,而且主线程会等待,直到任务结束并返回结果。
Task2与task1相似,Task2通过RunSynchronously()方法运行的。这个任务运行在主线程中,这个任务的输出与TaskOper方法输出结果一样。这就是task的优势,可以使用task对TaskOper方法进行优化,可以避免使用线程池来执行一些执行时间非常短的操作。
Task3运行task1的方法,但是这次没有阻塞主线程,只是在任务完成之前循环打印出任务状态。