《CLR via C#》之线程处理——线程池与任务

《CLR via C#》之线程处理——线程池与任务

《CLR via C#》之线程处理——线程池与任务
线程池
线程池用法
任务
等待任务完成并获取结果(Wait方法和Result属性)
取消任务(token的ThrowIfCancellationRequested方法)
任务完成自动启动新任务(ContinueWith方法)
任务启动子任务
任务工厂(TaskFactory和TaskFactory)

线程池

每一个CLR都有一个线程池——由这个CLR控制的所有AppDomain共享。如果一个进程加载了多个CLR,那么每个CLR都有它自己的线程池。

CLR初始化化时,线程池是空的。在内部线程池维护了一个操作请求队列。应用请求异步操作时,就将一个记录项添加到队列中。线程池从队列中提取这个记录项,将这个记录项Dispatch给一个线程池线程。如果线程池没有线程,就创建一个新线程。当线程执行完后,也不会被销毁。

如果线程池闲着没事干一段时间之后,线程会终止自己以释放资源。

线程池用法

通常,调用ThreadPool类的静态方法:

  1. static Boolean QueueUserWorkItem(WaitCallback callBack);//callBack: 回调委托
  2. static Boolean QueueUserWorkItem(WaitCallback callBa ck, Object state);//state:回调参数

任务

使用ThreadPool.QueueUserWorkItem方法有两个限制:

  1. 没有内建机制通知操作何时完成;
  2. 没有机制在操作完成时获得返回值。

因此,推出了Task来完成相同的事

  1. ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);//调用QueueUserWorkItem
  2. new Task(ComputeBoundOp, 5).Start(); //用Task做相同的事
  3. Task.Run(() => ComputeBoundOp(5)); //等价写法

在构造Task时,可以传递TaskCreationOptions标识来控制Task的执行方式。

  1. [Flags, Serializable]
  2. public enum TaskCreationOptions
  3. {
  4. None = 0x0000, //默认
  5. // 提议TaskScheduler,希望尽快执行
  6. PreferFairness = 0x0001,
  7. // 提议TaskScheduler,希望尽可能创建线程池线程
  8. LongRunning = 0x0002,
  9. // 总被采纳:将Task和它的父Task关联
  10. AttachedToParent = 0x0004,
  11. // 总被采纳:如果一个Task试图和父Task连接,它就是个普通Task,而不是子Task
  12. DenyChidAttach = 0x0008,
  13. // 总被采纳:强迫子Task使用默认调度器而不是父Task的调度器
  14. HideScheduler = 0x0010
  15. }

等待任务完成并获取结果(Wait方法和Result属性)

  1. Task<int> t = new Task<int>(n => Sum((int)n), 100000000, TaskCreationOptions.LongRunning);
  2. t.Start();
  3. // 可选择显式等待任务完成
  4. t.Wait();
  5. // 可获得结果(Result属性内部会调用Wait)
  6. Console.WriteLine("The Sum is " + t.Result);

注意:Result属性内部会调用Wait方法。


取消任务(token的ThrowIfCancellationRequested方法)

Task和ThreadPool取消任务的区别是,前者在操作中调用用ThrowIfCancellationRequested抛出异常,后者检查IsCancellationRequested属性。原因是Task可以返回值,需要将已完成的任务和出错的任务区分开。

  1. internal static class TaskDemo
  2. {
  3. private static Int32 Sum(CancellationToken token, int n)
  4. {
  5. int sum = 0;
  6. for (; n > 0; n--)
  7. {
  8. // 如果token已取消,调用该方法会抛出一个OperationCanceledException
  9. token.ThrowIfCancellationRequested();
  10. checked
  11. {
  12. sum += 10;
  13. }
  14. }
  15. return sum;
  16. }
  17. public static void Go()
  18. {
  19. CancellationTokenSource cts = new CancellationTokenSource();
  20. // 创建Task,并立即Start
  21. Task<int> t = Task.Run(() => Sum(cts.Token, 100000000), cts.Token);
  22. // 在之后的某个时间,取消task
  23. cts.Cancel();
  24. try
  25. {
  26. //如果任务已经取消,Result会抛出AggregateException
  27. Console.WriteLine("The sum is :" + t.Result);
  28. }
  29. catch(AggregateException x)
  30. {
  31. // 将任何OperationCancelException都视为已处理。
  32. // 其它任何异常都造成抛出一个新的AggregateException,其中只包含未处理的异常。
  33. x.Handle(e => e is OperationCanceledException);
  34. Console.WriteLine("Sum was cancelled");
  35. }
  36. }
  37. }

注意:如果试图取消一个未Start的任务,会抛出InvalidOperationException。


任务完成自动启动新任务(ContinueWith方法)

  1. public static void TaskContinueWith()
  2. {
  3. Task<int> t = Task.Run(() => Sum(10000));
  4. t.ContinueWith( task => Console.WriteLine("The sum is: " + task.Result),
  5. TaskContinuationOptions.OnlyOnRanToCompletion);
  6. t.ContinueWith(task => Console.WriteLine("Sum threw: " + task.Exception.InnerException),
  7. TaskContinuationOptions.OnlyOnFaulted);
  8. //ContinueWith返回Task,但一般都不需要保存它
  9. Task cwt = t.ContinueWith(task => Console.WriteLine("Sum was cancelled"),
  10. TaskContinuationOptions.OnlyOnCanceled);
  11. }

TaskContinuationOptions枚举类型的前六个标识与TaskCreationOptions一致:

  1. [Flags, Serializable]
  2. public enum TaskContinuationOptions
  3. {
  4. None = 0x0000,// 默认
  5. // 提议TaskScheduler,希望任务尽快执行
  6. PreferFairness = 0x0001,
  7. // 提议TaskScheduler:应尽可能创建线程池线程
  8. LongRunning = 0x0002,
  9. // 总被采纳:将一个Task与它的父Task关联
  10. AttachedToParent = 0x0004,
  11. // 任务试图和这个父任务连接将抛出一个InvalidOperationException
  12. DenyChildAttach = 0x0008,
  13. // 强迫子任务使用默认调度器而不是父任务的调度器
  14. HideScheduler = 0x0010,
  15. // 除非前置任务完成,否则禁止延续任务完成(取消)
  16. LazyCancellation = 0x0020,
  17. // 这个标识指出你希望由执行第一个任务的线程执行ContinueWith任务。
  18. // 第一个任务完成后,调用ContinueWith的线程接着执行ContinueWith任务。
  19. ExecuteSynchronously = 0x80000,
  20. // 这些标识指出在什么情况下运行ContinueWith任务
  21. NotOnRanToCompletion = 0x10000,
  22. NotOnFaulted = 0x20000,
  23. NotOnCanceled = 0x40000,
  24. // 这些标识是以上三个标识的便利组合
  25. OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted,
  26. OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled,
  27. OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled,
  28. }

注意:ExecuteSynchronously:同步执行,两个任务在同一个线程一前一后地执行,被称为同步执行。


任务启动子任务

  1. public static void TaskParentDemo()
  2. {
  3. Task<Int32[]> parent = new Task<Int32[]>(() =>
  4. {
  5. var results = new Int32[3]; //创建一个数组来存储结果
  6. //这个任务创建并启动3个子任务
  7. new Task(() => results[0] = Sum(10000), TaskCreationOptions.AttachedToParent).Start();
  8. new Task(() => results[1] = Sum(20000), TaskCreationOptions.AttachedToParent).Start();
  9. new Task(() => results[2] = Sum(30000), TaskCreationOptions.AttachedToParent).Start();
  10. return results;
  11. });
  12. // 父任务及其子任务运行完成后,用一个延续任务显示结果
  13. var cwt = parent.ContinueWith(
  14. parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
  15. parent.Start();
  16. }

注意:一个Task创建的Task默认为顶级任务,与创建它的Task无关。但是使用TaskCreationOptions.AttachedToParent标志关联Task和创建它的Task,除非所有子Task结束,否则父Task不认为已经结束。


任务工厂(TaskFactory和TaskFactory)

有时需要创建一组共享相同配置的Task对象,可创建一个任务工厂来封装通用的配置。System.Threading.Tasks命名空间提供了TaskFactory和TaskFactory。

构造一个任务工厂,向它传递希望任务具有的CancellationToken,TaskScheduler,TaskCreationOptions和TaskContinuationOptions设置。

  1. public static void TaskFactoryDemo()
  2. {
  3. Task parent = new Task(() =>
  4. {
  5. var cts = new CancellationTokenSource();
  6. var tf = new TaskFactory<Int32>(
  7. cts.Token,
  8. TaskCreationOptions.AttachedToParent,
  9. TaskContinuationOptions.ExecuteSynchronously,
  10. TaskScheduler.Default);
  11. var childTasks = new[]
  12. {
  13. tf.StartNew(() => Sum(cts.Token, 10000)),
  14. tf.StartNew(() => Sum(cts.Token, 20000)),
  15. tf.StartNew(() => Sum(cts.Token, Int32.MaxValue))
  16. };
  17. // 任何子任务抛出异常,就取消其余子任务
  18. for (int task = 0; task < childTasks.Length; task++)
  19. {
  20. childTasks[task].ContinueWith(
  21. t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
  22. }
  23. // 所有子任务完成后,从未出错/未取消的任务获取返回的最大值
  24. // 然后将最大值传给另一个任务来显示最大结果。
  25. // **注意**:由于这个任务由任务工厂创建,所以会任务是父任务的一个子任务(默认配置)
  26. // 显示CancellationToken.None,和TaskContinuationOptions.ExecuteSynchronously覆盖默认值
  27. tf.ContinueWhenAll(
  28. childTasks,
  29. completedTasks => completedTasks.Where(
  30. t => !t.IsFaulted && !t.IsCanceled).Max(t => t.Result),
  31. CancellationToken.None)
  32. .ContinueWith(t => Console.WriteLine("The maxium is:" + t.Result),
  33. TaskContinuationOptions.ExecuteSynchronously);
  34. });
  35. // 子任务完成后,显示任何未处理的异常
  36. parent.ContinueWith(p =>
  37. {
  38. StringBuilder sb = new StringBuilder(
  39. "The following exception(s) occurred:" + Environment.NewLine);
  40. foreach (var e in p.Exception.Flatten().InnerExceptions)
  41. {
  42. sb.AppendLine(" " + e.GetType().ToString());
  43. }
  44. Console.WriteLine(sb.ToString());
  45. }, TaskContinuationOptions.OnlyOnFaulted);
  46. parent.Start();
  47. }

注意:任务工厂的ContinueWhenAll和ContinueWhenAny时,NotOnRanToCompletion,NotOnFaulted和NotOnCanceled标识是非法的。组合标识当然也是非法的(OnlyOnCanceled,OnlyOnFaulted 和OnlyOnRanToCompletion)也是非法的。即,无论前置任务是如何完成的,ContinueWhenAll和ContinueWhenAny都会执行延续任务。

来自为知笔记(Wiz)

时间: 2024-07-29 16:21:34

《CLR via C#》之线程处理——线程池与任务的相关文章

(CLR via C#学习笔记)异步操作 - 线程池

一 线程池基础 1.线程池维护了一个操作请求队列,将请求的操作追加到线程池队列中,线程池的代码从队列中提取操作项,派发给线程池中的线程; 2.CLR初始化时,线程池中是没有线程的,当有操作派发给线程池时,如果线程池中没有线程或者没有空闲状态的线程,将会创建一个新的线程执行派发的操作,如果有空闲状态的线程,将直接派发一个空闲状态的线程执行操作; 3.线程池线程完成操作任务后,线程不会被销毁,而是返回线程池,进入空闲状态,等待响应另一个派发请求;4.当一个线程池线程处于空闲状态一段时间后(不同的CL

线程系列04,传递数据给线程,线程命名,线程异常处理,线程池

本篇体验:如何传递数据给线程,如何给线程命名,线程的异常处理,线程池.实在是太基础的部分. □ 传递数据给线程 ※ 使用Lambda表达式 class Program { static void Main(string[] args) { Thread t = new Thread(() => Say("hello", "world")); t.Start(); } static void Say(string msg, string msg1) { Cons

菜鸟之旅——学习线程(线程和线程池)

愉悦的绅士 菜鸟之旅--学习线程(线程和线程池) 上一篇主要介绍了进程和线程的一些基本知识,现在回归正题,我们来学一下线程的使用,本篇主要是使用新建线程和线程池的方式. 线程 先来介绍简单的线程使用:使用new方法来创建线程,至于撤销线程,我们不必去管(我也不知道怎么去管XD),因为CLR已经替我们去管理了. 创建 先来看一个简单的使用线程的例子: static void Main(string[] args) { Thread t1 = new Thread(Menthod1); Thread

饭店的运作模式(线程、线程池、任务)

前台线程(服务员.大堂经理):主程序必须等待线程执行完毕后才可退出程序.Thread默认为前台线程,也可以设置为后台线程 后台线程(厨师.配菜):主程序执行完毕后就退出,不管线程是否执行完毕.ThreadPool默认为后台线程 所有前台线程停止运行时,CLR强制终止仍在运行的任何后台线程,后台线程被直接终止:不抛出异常.(前台服务员都停止工作了,说明客户都走光了.你后台厨师.配菜还忙个什么劲!) 线程消耗:开启一个新线程,线程不做任何操作,都要消耗1M左右的内存(这些个服务员.大堂经理.厨师什么

InnoDB 存储引擎的线程与内存池

InnoDB 存储引擎的线程与内存池 InnoDB体系结构如下: 后台线程: 1.后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据: 2.另外,将以修改的数据文件刷新到磁盘文件: 3.同时,保证在数据库发生异常的情况下,InnoDB能恢复到正常运行状态. 内存池:InnoDB有多个内存块,这些内存块组成了一个大的内存池.这些内存块包括有:缓冲池(innodb_buffer_pool)和日志缓冲(log_buffer)以及额外内存池(innodb_addtional

线程和线程池

首先线程有守护线程和用户线程两种,区别就是用户线程是否保持程序的运行状态.当程序在运行时,必定有一个或以上的线程是用户线程,而当程序结束时,所有守护线程也都将被关闭.使用Thread.setDaemon(ture)可以把线程标记为守护线程,默认线程状态继承自创建它的线程.线程的两种创建方法不多说了. 线程安全一般指的是共享变量被多个线程访问读写造成的数据不一致或者是数据不完整性.一般有如下几种方法可供参考: 1.synchronized方法,提供只能供一个线程访问的类,方法或语句块,控制变量的修

java 线程、线程池基本应用示例代码回顾

package org.rui.thread; /** * 定义任务 * * @author lenovo * */ public class LiftOff implements Runnable { protected int countDown=10; private static int taskCount=0; private final int id=taskCount++; public LiftOff(){} public LiftOff(int countDown) { thi

Android的线程和线程池

原文链接,转载请注明出处 http://sparkyuan.me/2016/03/25/Android的线程和线程池/ 在Java中默认情况下一个进程只有一个线程,也就是主线程,其他线程都是子线程,也叫工作线程.Android中的主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作.线程的创建和销毁的开销较大,所以如果一个进程要频繁地创建和销毁线程的话,都会采用线程池的方式. Android中线程的形态 传统的Thread AsyncTask HandlerThread IntentS

线程与线程池,实例比较。

线程池: int count = 200000; long startTime = System.currentTimeMillis(); final List<Integer> l = new LinkedList<Integer>(); ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(count))