本系列首页链接:[C#多线程编程系列(一)- 简介 ]
1.1 简介在之前的几个章节中,就线程的使用和多线程相关的内容进行了介绍。因为线程涉及到异步、同步、异常传递等问题,所以在项目中使用多线程的代价是比较高昂的,需要编写大量的代码来达到正确性和健壮性。
为了解决这样一些的问题,在.Net Framework 4.0中引入了一个关于一步操作的API。它叫做任务并行库(Task Parallel Library)。然后在.Net Framwork 4.5中对它进行了轻微的改进,本文的案例都是用最新版本的TPL库,而且我们还可以使用C# 5.0的新特性await/async来简化TAP编程,当然这是之后才介绍的。
TPL内部使用了线程池,但是效率更高。在把线程归还回线程池之前,它会在同一线程中顺序执行多少Task,这样避免了一些小任务上下文切换浪费时间片的问题。
任务是对象,其中封装了以异步方式执行的工作,但是委托也是封装了代码的对象。任务和委托的区别在于,委托是同步的,而任务是异步的。
在本章中,我们将会讨论如何使用TPL库来进行任务之间的组合同步,如何将遗留的APM和EAP模式转换为TPL模式等等。
1.2 创建任务在本节中,主要是演示了如何创建一个任务。其主要用到了System.Threading.Tasks命名空间下的Task类。该类可以被实例化并且提供了一组静态方法,可以方便快捷的创建任务。
在下面实例代码中,分别延时了三种常见的任务创建方式,并且创建任务是可以指定任务创建的选项,从而达到最优的创建方式。
在TaskCreationOptions中一共有7个枚举,枚举是可以使用|运算符组合定义的。其枚举如下表所示。
成员名称 说明AttachedToParent 指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意,如果使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。有关详细信息,请参阅附加和分离的子任务。
DenyChildAttach 指定任何尝试作为附加的子任务执行(即,使用 AttachedToParent 选项创建)的子任务都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务。
HideScheduler 防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 Default 当前计划程序。
LongRunning 指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 TaskScheduler 提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。
None 指定应使用默认行为。
PreferFairness 提示 TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
RunContinuationsAsynchronously 强制异步执行添加到当前任务的延续任务。请注意,RunContinuationsAsynchronously 成员在以 .NET Framework 4.6 开头的 TaskCreationOptions 枚举中可用。
static void Main(string[] args) { // 使用构造方法创建任务 var t1 = new Task(() => TaskMethod("Task 1")); var t2 = new Task(() => TaskMethod("Task 2")); // 需要手动启动 t2.Start(); t1.Start(); // 使用Task.Run 方法启动任务 不需要手动启动 Task.Run(() => TaskMethod("Task 3")); // 使用 Task.Factory.StartNew方法 启动任务 实际上就是Task.Run Task.Factory.StartNew(() => TaskMethod("Task 4")); // 在StartNew的基础上 添加 TaskCreationOptions.LongRunning 告诉 Factory该任务需要长时间运行 // 那么它就会可能会创建一个 非线程池线程来执行任务 Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning); ReadLine(); } static void TaskMethod(string name) { WriteLine($"任务 {name} 运行,线程 id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}."); }
运行结果如下图所示。