C#多线程编程系列(五)- 使用任务并行库

目录

目录

  • 劈头盖脸质大学纲
  • 一、前言
  • 二、目录结构
  • 四、章节结构
  • 五、相关链接
  • 1.1
    简介
  • 1.2
    创造义务
  • 1.3
    使用任务实行基本的操作
  • 1.4
    组合职分
  • 1.5
    将APM方式调换为任务
  • 1.6
    将EAP情势转变为任务
  • 1.7
    达成撤除选项
  • 1.8
    管理任务中的万分
  • 1.9
    彼此运维职责
  • 1.10
    使用TaskScheduler配置职责实施
  • 参照书籍
  • 小编水平有限,要是不当款待各位争论指正!



本体系首页链接:[C#多线程编制程序连串(一)-
简要介绍 ]

多种大纲

前段时间只整理到第二章,线程同步,我前边会慢慢更新,争取能把这本书中优良的学问都享受出去。
C#多线程编制程序种类(一)-
简单介绍
C#三十二线程编制程序体系(二)-
线程基础
C#四线程编制程序类别(三)-
线程同步
C#三十二线程编制程序体系(四)-
使用线程池
C#多线程编制程序连串(五)-
使用职责并行库

源码下载点击链接
示范源码下载


一、前言

在C#上学进度中,十二线程一贯都以比较难的一些,因为内部涉及到相当多与操作系统相关的知识。比如:怎么样开展多线程编制程序、线程同步、线程锁、线程异步、并行编制程序、并行集结等等的学识。所以作者在就学进度中也是碰见了重重困难,并且直接未有好的学科。

不过作者在浏览GitHub时,开掘有大佬已经引入了一本新书,《MULTITHREADING
WITH C# COOKBOOK SECOND
EDITION》
,当中最首要正是讲哪些在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方式等等。

二、目录结构

本书一共分为二十个章节,分别从线程基础、线程同步、线程池、Task并行库、C#
6.0脾气、并发集合类、PLINQ、反应式编制程序、异步I/O、并行产生格局和在UWP
.Net Core中利用来成功的介绍了C#八线程编制程序。如下图所示。

图片 1

屈居百度脑图链接

作者认为本书确实是一本博闻强识的好书,回看起这段被四线程虐过的生活。粗略的过了一次今后就希图马上拿出来分享给大家,后文有连锁的购买贩卖链接,我们也足以向来在某宝、某东寻找关键字,价格也是比较平价的,多多支持正版。

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}.");
}

运营结果如下图所示。

图片 2

四、章节结构

本书首尽管偏实施应用有的,个中每一个章节中的本事验证都分为多少个部分,策画干活(Getting
ready)、实现格局(How to do it…)和促成原理(How it works…)

正文节节选第一章的率先小节比方,首先是计划干活。

1.预备干活

图片 3

2.达成方式

图片 4图片 5

3.贯彻原理

图片 6

国外的书平常都以相比偏理论,像这种理论和实践结合的照旧少之甚少,所以第不经常间推荐给我们。

1.3 使用任务奉行基本的操作

在本节中,使用职务实施基本的操作,何况获得职分推行到位后的结果值。本节内容比较简单,在此不做过多介绍。

示范代码如下,在主线程中要博得结果值,常用的方式就是访谈task.Result个性,假使职务线程还没实行完成,那么会阻塞主线程,直到线程实施完。借使职责线程施行完结,那么将一向得到运算的结果值。

Task 3中,使用了task.Status来打字与印刷线程的意况,线程每种意况的现实性意思,就要下一节中介绍。

static void Main(string[] args)
{
    // 直接执行方法 作为参照
    TaskMethod("主线程任务");

    // 访问 Result属性 达到运行结果
    Task<int> task = CreateTask("Task 1");
    task.Start();
    int result = task.Result;
    WriteLine($"运算结果: {result}");

    // 使用当前线程,同步执行任务
    task = CreateTask("Task 2");
    task.RunSynchronously();
    result = task.Result;
    WriteLine($"运算结果:{result}");

    // 通过循环等待 获取运行结果
    task = CreateTask("Task 3");
    WriteLine(task.Status);
    task.Start();

    while (!task.IsCompleted)
    {
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(0.5));
    }

    WriteLine(task.Status);
    result = task.Result;
    WriteLine($"运算结果:{result}");

    Console.ReadLine();
}

static Task<int> CreateTask(string name)
{
    return new Task<int>(() => TaskMethod(name));
}

static int TaskMethod(string name)
{
    WriteLine($"{name} 运行在线程 {CurrentThread.ManagedThreadId}上. 是否为线程池线程 {CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(2));

    return 42;
}

运转结果如下,可知Task 1
Task 2均是运作在主线程上,并非线程池线程。

图片 7

五、相关链接

沾满购买地点,大家要么多多支撑正版.

《MULTITHREADING WITH C# COOKBOOK SECOND
EDITION》选购地点

补给,本书有汉语翻译版本,由黄博文大佬翻译,可是好像依旧第一版。

《C#四线程编制程序实战》购买地方

1.4 组合职分

在本节中,呈现了任务之中三个无敌的成效,那正是整合任务。通过整合职分可很好的汇报任务与义务之间的异步、同步关系,大大减弱了编制程序的难度。

组成职务首倘使通过task.ContinueWith()task.WhenAny()task.WhenAll()等和task.GetAwaiter().OnCompleted()主意来落实。

在使用task.ContinueWith()艺术时,要求注意它也可传递一雨后鞭笋的枚举选项TaskContinuationOptions,该枚举选项和TaskCreationOptions好像,其现实定义如下表所示。

成员名称 说明
AttachedToParent 如果延续为子任务,则指定将延续附加到任务层次结构中的父级。 只有当延续前面的任务也是子任务时,延续才可以是子任务。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意,如果使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。有关更多信息,请参见Attached and Detached Child Tasks
DenyChildAttach 指定任何使用 TaskCreationOptions.AttachedToParent 选项创建,并尝试作为附加的子任务执行的子任务(即,由此延续创建的任何嵌套内部任务)都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务
ExecuteSynchronously 指定应同步执行延续任务。 指定此选项后,延续任务在导致前面的任务转换为其最终状态的相同线程上运行。如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。 如果前面任务的 CancellationTokenSource 已在一个 finally(在 Visual Basic 中为 Finally)块中释放,则使用此选项的延续任务将在该 finally 块中运行。 只应同步执行运行时间非常短的延续任务。由于任务以同步方式执行,因此无需调用诸如 Task.Wait 的方法来确保调用线程等待任务完成。
HideScheduler 指定由延续通过调用方法(如 Task.RunTask.ContinueWith)创建的任务将默认计划程序 (TaskScheduler.Default) 视为当前的计划程序,而不是正在运行该延续的计划程序。
LazyCancellation 在延续取消的情况下,防止延续的完成直到完成先前的任务。
LongRunning 指定延续将是长期运行的、粗粒度的操作。 它会向 TaskScheduler 提示,过度订阅可能是合理的。
None 如果未指定延续选项,应在执行延续任务时使用指定的默认行为。 延续任务在前面的任务完成后以异步方式运行,与前面任务最终的 Task.Status 属性值无关。 如果延续为子任务,则会将其创建为分离的嵌套任务。
NotOnCanceled 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
NotOnFaulted 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。 此选项对多任务延续无效。
NotOnRanToCompletion 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
OnlyOnCanceled 指定只应在延续前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
OnlyOnFaulted 指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。OnlyOnFaulted 选项可保证前面任务中的 Task.Exception 属性不是 null。 你可以使用该属性来捕获异常,并确定导致任务出错的异常。 如果你不访问 Exception 属性,则不会处理异常。 此外,如果尝试访问已取消或出错的任务的 Result 属性,则会引发一个新异常。此选项对多任务延续无效。
OnlyOnRanToCompletion 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
PreferFairness 提示 TaskScheduler 按任务计划的顺序安排任务,因此较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
RunContinuationsAsynchronously 指定应异步运行延续任务。 此选项优先于 TaskContinuationOptions.ExecuteSynchronously。

演示代码如下所示,使用ContinueWith()OnCompleted()主意结合了职分来运作,搭配差别的TaskCreationOptionsTaskContinuationOptions来贯彻分裂的意义。

static void Main(string[] args)
{
    WriteLine($"主线程 线程 Id {CurrentThread.ManagedThreadId}");

    // 创建两个任务
    var firstTask = new Task<int>(() => TaskMethod("Frist Task",3));
    var secondTask = new Task<int>(()=> TaskMethod("Second Task",2));

    // 在默认的情况下 ContiueWith会在前面任务运行后再运行
    firstTask.ContinueWith(t => WriteLine($"第一次运行答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}"));

    // 启动任务
    firstTask.Start();
    secondTask.Start();

    Sleep(TimeSpan.FromSeconds(4));

    // 这里会紧接着 Second Task运行后运行, 但是由于添加了 OnlyOnRanToCompletion 和 ExecuteSynchronously 所以会由运行SecondTask的线程来 运行这个任务
    Task continuation = secondTask.ContinueWith(t => WriteLine($"第二次运行的答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}"),TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);

    // OnCompleted 是一个事件  当contiuation运行完成后 执行OnCompleted Action事件
    continuation.GetAwaiter().OnCompleted(() => WriteLine($"后继任务完成. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程 {CurrentThread.IsThreadPoolThread}"));

    Sleep(TimeSpan.FromSeconds(2));
    WriteLine();

    firstTask = new Task<int>(() => 
    {
        // 使用了TaskCreationOptions.AttachedToParent 将这个Task和父Task关联, 当这个Task没有结束时  父Task 状态为 WaitingForChildrenToComplete
        var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task",5), TaskCreationOptions.AttachedToParent);

        innerTask.ContinueWith(t => TaskMethod("Thrid Task", 2), TaskContinuationOptions.AttachedToParent);

        return TaskMethod("First Task",2);
    });

    firstTask.Start();

    // 检查firstTask线程状态  根据上面的分析 首先是  Running -> WatingForChildrenToComplete -> RanToCompletion
    while (! firstTask.IsCompleted)
    {
        WriteLine(firstTask.Status);

        Sleep(TimeSpan.FromSeconds(0.5));
    }

    WriteLine(firstTask.Status);

    Console.ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务 {name} 正在运行,线程池线程 Id {CurrentThread.ManagedThreadId},是否为线程池线程: {CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));

    return 42 * seconds;
}

启动结果如下图所示,与预期结果一致。个中使用了task.Status来打字与印刷职责局转的处境,对于task.Status的景况具体意思如下表所示。

成员名称 说明
Canceled 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。 有关详细信息,请参阅任务取消
Created 该任务已初始化,但尚未被计划。
Faulted 由于未处理异常的原因而完成的任务。
RanToCompletion 已成功完成执行的任务。
Running 该任务正在运行,但尚未完成。
WaitingForActivation 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingForChildrenToComplete 该任务已完成执行,正在隐式等待附加的子任务完成。
WaitingToRun 该任务已被计划执行,但尚未开始执行。

图片 8

发表评论

电子邮件地址不会被公开。 必填项已用*标注