异步多线程 Task理解
一、简介
Task是.NET Framework4.0 TPL(任务并行库)提供的新的操作线程池线程的封装类。它提供等待、终止(取消)、返回值、完成通知、失败通知、控制执行的先后次序等优化线程操作功能。Task(任务)并不是线程,任务运行的时候需要使用线程,但并不是说任务取代了线程,任务代码是使用底层的线程(Thread或ThreadPool线程)运行的,任务与线程之间并没有一对一的关系。
二、Task创建与启动
Task类创建的任务使用的是后台线程,所以在前台线程全部终止的时候,如果任务还没有全部执行完,就会被被动终止。创建一个新的任务时,任务调度器(默认是基于线程池的TaskScheduler调度器,ThreadPoolTaskScheduler)会找到一个最合适的工作者线程,然后将任务加入该工作者线程的本地队列(每个工作者线程都有对应本地队列),任务所包含的代码会在该线程中运行。
先定义一个任务要执行的方法:
static void NewTask() { Console.WriteLine("开始一个任务"); Console.WriteLine("Task id:{0}",Task.CurrentId); Console.WriteLine("任务执行完成"); }
再创建和启动一个任务,有以下三种方式:
1、使用TaskFactory创建一个任务
TaskFactory tf = new TaskFactory();
//任务就会立即启
Task t1 = tf.StartNew(NewTask);
2、使用Task类的Factory创建一个任务
Task t2 = Task.Factory.StartNew(NewTask);
3、Task构造函数并Start
Task t3 = new Task(NewTask);
t3.Start();
Task t4 = new Task(NewTask, TaskCreationOptions.PreferFairness);
t4.Start();
//因为任务是后台线程,所以我们这里阻塞主线程一秒钟来等待任务全部执行完成
Thread.Sleep(1000);
注意:使用Task类的构造函数(第3种)和TaskFactory类的StartNew()方法(第2种)时,都可以传递TaskCreationOptions枚举中的值。TaskCreationOptions如下:
注意:如果当前Task上的TaskCreationOptions设置为LongRunning的话,这个task就会委托到Thread中去执行,长时间运行的task占用着ThreadPool的线程,这时候ThreadPool为了保证线程充足,会再次开辟一些Thread,如果耗时任务此时释放了,会导致ThreadPool线程过多,上下文切换频繁,所以此时让Task在Thread中执行还是非常不错的选择,当然不指定这个LongRunning的话,就是在ThreadPool上执行。
三、Task状态与生命周期
任务Task有以下代表任务完成时状态的属性:
1、IsCanceled,因为被取消而完成
2、IsCompleted,成功完成
3、IsFaulted,因为发生异常而完成
任务并没有提供回调事件来通知完成(像BackgroundWorker一样),通过启用一个新任务的方式来完成类似的功能。 ContinueWith方法可以在一个任务完成的时候发起一个新任务,天然支持了任务的完成通知,可以在新任务中获取原任务的结果值。
四、Parallel
使用Parallel.For、Parallel.ForEach的循环迭代的并行执行,TPL会在后台创建System.Threading.Tasks.Task的实例。使用Parallel.Invoke时,TPL也会创建与调用的委托数目一致的System.Threading.Tasks.Task的实例。
五、Task异常处理
当很多任务并行运行的时候,可能会并行发生很多异常。Task实例能够处理一组一组的异常,这些异常有System.AggregateException类处理。
class Program { private static ConcurrentQueue<Product> queue = null; static void Main(string[] args) { queue = new ConcurrentQueue<Product>(); System.Threading.CancellationTokenSource token = new CancellationTokenSource(); Task tk1 = Task.Factory.StartNew(() => SetProduct(token.Token)); Thread.Sleep(2000);
//检查状态,是否因为异常而导致失败 if (tk1.IsFaulted) { //循环输出异常 foreach (Exception ex in tk1.Exception.InnerExceptions) { Console.WriteLine("tk1 Exception:{0}", ex.Message); } } Console.ReadLine(); } static void SetProduct(System.Threading.CancellationToken ct) { for (int i = 0; i < 5; i++) { throw new Exception(string.Format("Exception Index {0}", i)); } Console.WriteLine("SetProduct 执行完成"); } }
六、Task取消
通过取消标记来中断Task实例的执行。 CancellationTokenSource,CancellationToken下的IsCanceled属性标志当前是否已经被取消,取消任务,任务也不一定会马上取消。
class Program { private static ConcurrentQueue<Product> queue = null; /* coder:释迦苦僧 */ static void Main(string[] args) { queue = new ConcurrentQueue<Product>(); System.Threading.CancellationTokenSource token = new CancellationTokenSource(); Task tk1 = Task.Factory.StartNew(() => SetProduct(token.Token)); Task tk2 = Task.Factory.StartNew(() => SetProduct(token.Token)); Thread.Sleep(10); //取消任务操作 token.Cancel(); try { //等待完成 Task.WaitAll(new Task[] { tk1, tk2 }); } catch (AggregateException ex) { //如果当前的任务正在被取消,那么还会抛出一个TaskCanceledException异常,这个异常包含在AggregateException异常中 Console.WriteLine("tk1 Canceled:{0}", tk1.IsCanceled); Console.WriteLine("tk1 Canceled:{0}", tk2.IsCanceled); } Thread.Sleep(2000); Console.WriteLine("tk1 Canceled:{0}", tk1.IsCanceled); Console.WriteLine("tk1 Canceled:{0}", tk2.IsCanceled); Console.ReadLine(); } static void SetProduct(System.Threading.CancellationToken ct) { //每一次循环迭代,都会有新的代码调用 ThrowIfCancellationRequested //这行代码能够对 OpreationCanceledException 异常进行观察 //并且这个异常的标记与Task实例关联的那个标记进行比较,如果两者相同 ,而且IsCancelled属性为True,那么Task实例就知道存在一个要求取消的请求,并且会将状态转变为Canceled状态,中断任务执行。 //如果当前的任务正在被取消,那么还会抛出一个TaskCanceledException异常,这个异常包含在AggregateException异常中 //检查取消标记 ct.ThrowIfCancellationRequested(); for (int i = 0; i < 50000; i++) { Product model = new Product(); model.Name = "Name" + i; model.SellPrice = i; model.Category = "Category" + i; queue.Enqueue(model); ct.ThrowIfCancellationRequested(); } Console.WriteLine("SetProduct 执行完成"); } } class Product { public string Name { get; set; } public string Category { get; set; } public int SellPrice { get; set; } }
七、Task.WaitAll 并限定时长
10毫秒没有完成任务,则输出了****
queue = new ConcurrentQueue<Product>(); Task tk1 = new Task(() => { SetProduct(1); SetProduct(3);}); Task tk2 = new Task(() => SetProduct(2)); tk1.Start(); tk2.Start(); //如果tk1 tk2 没能在10毫秒内完成 则输出 ***** if (!Task.WaitAll(new Task[] { tk1, tk2 }, 10)) { Console.WriteLine("******"); } Console.ReadLine();
八、Task返回值 Task<TResult>
class Program { static void Main(string[] args) { Task<List<Product>> tk1 = Task<List<Product>>.Factory.StartNew(() => SetProduct()); Task.WaitAll(tk1); Console.WriteLine(tk1.Result.Count); Console.WriteLine(tk1.Result[0].Name); Console.ReadLine(); } static List<Product> SetProduct() { List<Product> result = new List<Product>(); for (int i = 0; i < 500; i++) { Product model = new Product(); model.Name = "Name" + i; model.SellPrice = i; model.Category = "Category" + i; result.Add(model); } Console.WriteLine("SetProduct 执行完成"); return result; } }
九、连续执行多个任务 ContinueWith
class Program { static void Main(string[] args) { //创建任务t1 Task t1 = Task.Factory.StartNew(() => { Console.WriteLine("执行 t1 任务"); SpinWait.SpinUntil(() => { return false; }, 2000); }); //创建任务t2 t2任务的执行 依赖与t1任务的执行完成 Task t2 = t1.ContinueWith((t) => { Console.WriteLine("执行 t2 任务"); SpinWait.SpinUntil(() => { return false; }, 2000); }); //创建任务t3 t3任务的执行 依赖与t2任务的执行完成 Task t3 = t2.ContinueWith((t) => { Console.WriteLine("执行 t3 任务"); }); Console.ReadLine(); } }