C#多线程学习笔记(四)任务并行库之Task

    在这个章节书中又一次提起:尽管使用任务调度器的开销比起添加线程来说通常都要小,但还是要考虑的。将两行代码作为两个独立的异步解决简单计算的任务运行就没有太大意义。无论何时我们都应该讨论:是否有必要并行化,是否能避免并行化。

二    Task

    一个Task表示一个异步操作。Task提供了很多的方法和属性来方便我们的控制和管理。多数属性的意义比较明显,这里只给出一部分解释。

    TaskStatus之初始状态:

    TaskStatus.Created,使用Task构造函数创建Task实例时的初始状态。当调用了Start或RunSynchronously方法时该状态会发生变化,当Task被取消时该状态也会发生变化;

    TaskStatus.WaitingForActivation,只有在其他依赖的任务完成之后才会得到调度的任务的初始状态,这种任务是使用定义延续任务(ContinueWith)的方法创建的。任务的生命周期从Created状态时真正开始,但是在这个Task的引用返回给调用者之前,这个任务的状态会被修改为TaskStaus.WaitingForActivation;

    TaskStatus.WaitingToRun,通过TaskFactory.StartNew(注,Task.Run是此方法的一个快捷方式)所创建的任务的初始状态。任务正在等待某个特定的调度器选择自己并运行。任务的生命周期从Created状态时真正开始,但是在这个Task的引用返回给调用者之前,这个任务的状态会被修改为TaskStaus.WaitingToRun。

    它们其实是对应了三种不同的建立Task的方式的,这里顺便贴一下另外找到的一则帖子,讲述了Task.Factory.StartNew()和new Task().Start()的区别所在:StartNew() vs new Task.Start().总结一下StartNew()更轻量,代价也更小,而Start()可能会在特定情况下使用,例如该帖子中提到的在Task的Action方法内部添加自己的延续任务的情况。

    TaskStatus之最终状态:

    TaskStatus.Canceled,在任务开始执行或执行过程中收到了取消的请求,IsCanceled属性将会被设置为true;

    TaskStatus.Faulted,在任务体中或相关联的子任务体中存在没有处理的异常,导致任务结束。IsFaulted属性将会被设置为true,Exception属性将会保持导致任务或相关联子任务提起结束的AggregateException;

扫描二维码关注公众号,回复: 1015876 查看本文章

    TaskStatus.RanToCompletion,任务完成执行。

    取消

    有关CancellationTokenSource和CancellationToken在线程池一节就已经提到过,这里再次复习,以上一节GenerateMD5Hashes的串行方法为例,我们尝试取消操作。

    

在Main函数中用了两种方式创建任务:

    值得注意的点是我们在创建任务的过程中传入了两次CancellationToken,后一个ct的意义在于:当任务实际启动前取消它时,该任务的TPL基础设施将可以对此取消操作进行处理。

    延续与链式任务

    在串联任务之前先学习一下TaskCreationOptions枚举值和TaskContinuationOptions枚举值。这些标志定义了任务创建和调度执行的一些行为,并可以通过位操作组合多个值。

    TaskCreationOptions:

    TaskCreationOptions.AttachedToParent,该任务与一个父任务关联。您可以在其他任务中创建任务;

    TaskCreationOptions.None,该任务可以使用默认的行为;

    TaskCreationOptions.LongRunning,该任务需要很长时间(通常在很多秒时就可以使用这个标志)运行,因此,调度器可以对这个任务使用粗粒度的操作;

    TaskCreationOptions.PreferFairness,这个标志告诉调度器,更早被调度的任务可能会更早的运行。它避免这个任务被底层线程池的局部队列和工作窃取机制(注1)放到局部队列中去。

    TaskContinuationOptions:

    TaskContinuationOptions.AttachedToParent,后续任务关联到了一个父任务;

    TaskContinuationOptions.ExecuteSynchronously,这个标志告诉调度器,后续任务应该使用将前序任务转入最终状态的同一个线程;

    TaskContinuationOptions.LongRunning,略;

    TaskContinuationOptions.None,无论前序任务最终的TaskStatus属性值是什么,后续任务都应该在前一个任务完成执行的时候调度运行(即使前序任务被取消了也会执行!)。

    TaskContinuationOptions.NotOnCanceled,如果前序任务是被取消的(TaskStatus == TaskStatus.Canceled),那么后续任务不该被调度;

    TaskContinuationOptions.NotOnFaulted,略;

    TaskContinuationOptions.NotOnRanToCompletion,略;

    TaskContinuationOptions.OnlyOnCanceled,略;

    TaskContinuationOptions.OnlyOnFaulted,略;

    TaskContinuationOptions.OnlyOnRanToCompletion,略;

    TaskContinuationOptions.PrefreFairness,略。

    TaskCreationOptions的值在TaskContinuationOptions中重复出现了,这是因为对于后续任务的创建来说,只应该使用TaskContinuationOptions这个枚举。

    可以在t1.Start()下面加上各种cts.Cancel()来观察执行情况,如果将throw语句注释掉又会怎样呢:


注1:局部队列

        通常当我们在主线程或者其他并没有分配给某个特定任务的线程的上下文中创建并启动任务时,这些任务会在底层线程池的全局队列中竞争工作项,这些任务称为顶层任务。然而如果是在其他任务的上下文中创建任务(ContinueWith),这时候可以充分利用局部队列改进所带来的好处。

    考虑这样的情形,每一个工作项(顶层任务)都需要创建很多的子工作线程,由于每个子工作线程完成得非常快,因此进入全局队列和移出全局队列的需求非常频繁,这可能会成为程序优化的瓶颈。因此,底层线程池引擎为每一个线程引入了一个局部队列,这样可以减少全局队列的争用。

    局部队列通常采用LIFO选择顺序。

    工作窃取

    当一个工作线程A在其局部队列中有很多工作项,而另一个工作线程B保持闲置时,该闲置线程B会尝试进入A的局部队列,从这个局部队列中“窃取”一个正在等待的任务,并执行这个任务。

    窃取的过程通常采用FIFO顺序。


    结合异步编程模型APM和基于事件的异步模式EAP

    线程非常消耗资源,当我们面对I/O密集型的工作流传时,不应该给每一个异步I/O操作都创建一个新的线程。这时可以通过Task.Factory.FromAsync方法运行大量所需的并发I/O操作,在这种情况下,我们可以编写自己的处理并发异步I/O代码,然后将复杂的工作都留给底层线程池引擎。此外,当并发I/O运行在不同的任务中时,可以通过解除UI线程的阻塞,从而向用户提供可响应的UI界面。但之前也提到过,现有的很多任务都是利用两个早期的异步模式:APM和EAP,这时候我们可以通过将它们转变为任务来简化代码,并提高可拓展性。

    举例说明,对于一个旧的APM模式,我们使用FromAsync将其转换为TPL模式的代码可能是这样的:

    PBar是一个进度条控件,Lbl是一个Label。以往进行异步操作时,为了避免UI线程阻塞我们先判断请求是否来自异线程(InvokeRequired),然后再使用控件的BeginInvoke和EndInvoke方法,现在则可以将这些更新UI的行为放置到一个延续任务中去,并设置其调度器为TaskScheduler.FromCurrentSynchronizationContext方法所创建的调度器,这个调度器能够将UI控件的更新工作发送给恰当的SynchronizationContext(SynchronizationContext是一种用于线程间通讯的对象,这里的“合适的”SynchronizationContext个人猜测是WindowsFormsSynchronizationContext之内的东西,),从而安全地更新控件。

    当然条件允许的情况下能直接使用TPL模式当然更好:

    需要注意的是,UI线程的能力也是有限的,如果我们将上面的100改为10000,同样可能得到阻塞的UI线程,这是由于过于频繁地更新请求使得UI线程已经不堪重负了。

猜你喜欢

转载自blog.csdn.net/saasanken/article/details/79593532