复习:

第三章内容中我们提到了三种异步编程模型,这里简单复习一下,分别如下

1.APM(异步编程模式):形如Beginxxx,Endxxx。

2.EAP(基于事件的异步编程模式):这个我们在.net中使用到了BackgroudWorker组件,使用方法是通过事件绑定处理的方式。

3.TPL(基于任务的异步编程模式):这个就会用到任务并行库。

4.1 简介

线程池相当于线程和用户之间的一个抽象层,向程序员隐藏了使用线程的细节,使得程序员专心处理程序逻辑,而不是各种线程问题。

但是使用线程池也很复杂。有两个问题存在:

①获取线程池中的工作线程的结果比较难

②实现线程池中工作线程执行的时序问题

综上,我们在第3章中提过的异步编程模型和基于事件的异步编程模型,这些模式使得获取结果更加容易,传播也更轻松,但是在进行多个异步操作组合的时候,还需要编写大量的代码。对于第二个问题.NET 4.0提出了一个新的关于异步操作的API。叫做任务并行库(Task Parallel Library 简称 TPL)。

TPL可以看成线程池之上的又一个抽象层,其对程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度的API。

TPL的核心概念是任务。一个任务代表了一个异步操作,该操作可以使用多种方式运行,可以使用或不使用独立线程运行。

一个任务可以有多种方式和其他任务组合起来。例如,可以同时执行多个任务,等待所有任务完成,然后运行一个任务对之前所有的任务结果进行一些计算。TPL与之前的模式相比,其中一个关键优势是其具有用于组合任务的便利的API。

处理任务中的异常结果也有多种方式。一个任务可以由多种任务组成,这些任务也可以有各自的子任务,所以有一个AggregateException的概念。这种异常可以捕获底层任务内部的所有异常,并允许单独处理这些异常。

C#5.0中可以使用await和async关键词以平滑的,舒服的方式进行操作任务。

4.2 创建任务

创建任务有两种方式:

1.直接创建任务实例,通过实例方法Start方法来启动任务

2.使用静态方法Task.Run和Task.Factory.StartNew来创建任务,两者都不需要显示的调用start方法启动任务,区别在于前者是后者的一种快捷方式,后者可以使用附加的选项。

例:
    class Program
    {
        static void Main(string[] args)
        {
            //第一种直接创建任务实例,需要用start方法来启动任务
            var t1 = new Task(() => TaskMethod("Task 1"));
            var t2 = new Task(() => TaskMethod("Task 2"));
            t2.Start();
            t1.Start();
          //第二种通过Task.Factory.StartNew来创建任务
          //这里Run方法只是Task.Factory.StartNew的一个快捷方式,Task.Factory.StartNew可以添加附加选项
          Task.Run(() => TaskMethod("Task 3"));
          Task.Factory.StartNew(() => TaskMethod("Task 4"));
          //我们标记了该任务是长时间任务,结果该任务没有使用线程池,而是在单独的线程中运行
          Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);
          Thread.Sleep(TimeSpan.FromSeconds());
        }         static void TaskMethod(string name)
        {
            Console.WriteLine(
                                "Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                 name,
                                Thread.CurrentThread.ManagedThreadId,
                                Thread.CurrentThread.IsThreadPoolThread);
        }
}

※由于没有对任务的时序做处理,所以多次执行每一次都可能不一样。

※Task5采用的是单独线程的方式来运行,但是根据运行该任务的当前的任务调度程序(task scheduler),运行方式可能会不同。

4.3使用任务执行基本的操作

主要介绍如何从任务中获取结果。

     class Program
    {
        static void Main(string[] args)
        {
             //启动主线程
             TaskMethod("Main Thread Task");
             //创建一个任务Task1,进行线程的同步
             Task<int> task = CreateTask("Task 1");
             task.Start();
            //阻塞主线程,直到线程执行完成
            int result = task.Result;
            Console.WriteLine("Result is: {0}", result);             //创建Taks2,使用RunSynchronously()方法进行同步
            task = CreateTask("Task 2");
            task.RunSynchronously();
            result = task.Result;
            Console.WriteLine("Result is: {0}", result);             //创建Task3,此时不进行主线程的阻塞
            task = CreateTask("Task 3");
            Console.WriteLine(task.Status);
            task.Start();             //循环打印task的状态,直到任务完成
            while (!task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            
            Console.WriteLine(task.Status);
            result = task.Result;
            Console.WriteLine("Result is: {0}", result);
        }         //创建一个新任务
        static Task<int> CreateTask(string name)
        {
            return new Task<int>(() => TaskMethod(name));
        }         //任务需要处理的方法
        static int TaskMethod(string name)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
            name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds());
            return ;
        }
}

执行结果:

4.4 组合任务

这里我会学习到如何将任务进行组合,以及父子任务之间的执行。废话不说,有码

实例1:

     class Program
    {
        static void Main(string[] args)
        {
            //打印主线程
            TaskMethod("Main Task", );
            //创建两个任务
            var firstTask = new Task<int>(() => TaskMethod("First Task", ));
            var secondTask = new Task<int>(() => TaskMethod("Second Task", ));             //设置firstTask的后续操作
            firstTask.ContinueWith(
                t => Console.WriteLine("The first answer is {0}. Thread id {1}, is thread pool thread: {2}",
                    t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
                TaskContinuationOptions.OnlyOnRanToCompletion);              //启动两个任务
            firstTask.Start();
            secondTask.Start();
            //延时4秒,足够两个任务完成的时间※↓这句是关键
            Thread.Sleep(TimeSpan.FromSeconds());             //为secondTask设置一个后续操作,TaskContinuationOptions.ExecuteSynchronously尝试同步方式执行后续操作
            Task continuation = secondTask.ContinueWith(
                t => Console.WriteLine("The second answer is {0}. Thread id {1}, is thread pool thread: {2}",
                    t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
                TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);             //为之前的后续操作也定义一个后续操作,这里使用了C#5.0的方法GetAwaiter().OnCompleted()
            continuation.GetAwaiter().OnCompleted(
                () => Console.WriteLine("Continuation Task Completed! Thread id {0}, is thread pool thread: {1}",
                    Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));             Thread.Sleep(TimeSpan.FromSeconds());
            Console.WriteLine();             Thread.Sleep(TimeSpan.FromSeconds());
        }         static int TaskMethod(string name, int seconds)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return * seconds;
        }
}

这里我们看到secondTask的后续操作没有使用到线程池,为什么呢?

解释:由上面的代码我们看到,使用了TaskContinuationOptions.ExecuteSynchronously尝试同步方式执行后续操作,如果后续操作时间非常短暂,使用上面的方式非常有效率的,因为放置在主线程进行运行要比放置在线程池中运行要快,那为啥会出现这样的情况呢,就是上面标记的延时代码的功劳,这段延时代码使得SecondTask后续操作正好得到了前面任务执行的结果。现在我把  Thread.Sleep(TimeSpan.FromSeconds(4));注释掉再试一下,结果如下:

感觉就像食堂打饭,两个人吃饭,A帮B打饭。

第一种是:A打完饭后,发现B刚来,就直接把饭给了B,然后B直接吃了

第二种是:A打饭的时候,B正好也来了,于是两人一起站队,A打完饭后再把饭给了B

例2:演示了一下父子任务之间的关系。

 class Program
    {
        static void Main(string[] args)
        {
             //创建一个父任务
             var firstTask = new Task<int>(() =>
            {
                //创建一个子任务,使用TaskCreationOptions.AttachedToParent来标识
                var innerTask = Task.Factory.StartNew(
                                        () => TaskMethod("Second Task", ),
                                        TaskCreationOptions.AttachedToParent);
              //创建一个子任务的后续操作,该后续操作也会影响父任务
               innerTask.ContinueWith(
                                        t => TaskMethod("Third Task", ),
                                        TaskContinuationOptions.AttachedToParent);
                return TaskMethod("First Task", );
            });             //启动任务
            firstTask.Start();             //循环打印任务的状态
            while (!firstTask.IsCompleted)
            {
                Console.WriteLine(firstTask.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(firstTask.Status);             Thread.Sleep(TimeSpan.FromSeconds());
        }         static int TaskMethod(string name, int seconds)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return * seconds;
        }

上面结果显示,父任务必须等待所有的子任务完成才能完成,但是看不出来他们是同步还是异步执行的。因为从First Task和Sencod Task它们之间的运行时序上也看不出来他们是父亲执行完了再执行的子任务,所以我觉得把父任务的时间调长一点,这回我让父任务执行10s

修改:

return TaskMethod("First Task", 2);  →   return TaskMethod("First Task", 10);

结果如下

这回显示的都是firstTask的Running状态,所以应该能肯定父子之间默认情况下也是异步执行的。因为父任务必须要等子任务全结束才能完事。

C#当中的多线程_任务并行库(上)的更多相关文章

  1. C#当中的多线程_任务并行库(下)

    4.8 处理任务中的异常 下面这个例子讨论了任务当中抛出异常,以及任务异常的获取     class Program     {         static void Main(string[] a ...

  2. C#当中的多线程_任务并行库(中)

    发现自己有点懒了!也可能是越往后越难了,看书理解起来有点费劲,所以这两天就每天更新一点学习笔记吧. 4.5 将APM模式转化为任务 书上提供的三种方式 方式一: class Program       ...

  3. C#多线程开发-任务并行库04

    你好,我是阿辉. 之前学习了线程池,知道了它有很多好处. 使用线程池可以使我们在减少并行度花销时节省操作系统资源.可认为线程池是一个抽象层,其向程序员隐藏了使用线程的细节,使我们可以专心处理程序逻辑, ...

  4. C#当中的多线程_线程池

    3.1 简介 线程池主要用在需要大量短暂的开销大的资源的情形.我们预先分配一些资源在线程池当中,当我们需要使用的时候,直接从池中取出,代替了重新创建,不用时候就送回到池当中. .NET当中的线程池是受 ...

  5. C#当中的多线程_线程同步

    第2章 线程同步 原来以为线程同步就是lock,monitor等呢,看了第二章真是大开眼界啊! 第一章中我们遇到了一个叫做竞争条件的问题.引起的原因是没有进行正确的线程同步.当一个线程在执行操作时候, ...

  6. C#当中的多线程_线程基础

    前言 最近工作不是很忙,想把买了很久了的<C#多线程编程实战>看完,所以索性把每一章的重点记录一下,方便以后回忆. 第1章 线程基础 1.创建一个线程 using System; usin ...

  7. Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介

    Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...

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

    目录 1.1 简介 1.2 创建任务 1.3 使用任务执行基本的操作 1.4 组合任务 1.5 将APM模式转换为任务 1.6 将EAP模式转换为任务 1.7 实现取消选项 1.8 处理任务中的异常 ...

  9. C#并行库(TaskParallelLibrary)用法小结

    今天有空,总结一下.NET 4.5并行库(TaskParallelLibrary)用法. 也许C和C++的程序员刚刚开始写C#还习惯于new Thread来新建一个线程,但新建线程需要内存和CPU上下 ...

随机推荐

  1. 如何在Azure环境里做好信息传递可扩展性经验分享

    作者 王枫 发布于2014年5月15日 综述 本文介绍建立一个在Azure上使用Azure服务总线, 高吞吐量短信平台的必要步骤.在这篇文章中提出的解决方案是在响应由客户的具体要求,建立一个基于Win ...

  2. eclipse 常用快捷键整理

    1.整理代码的快捷键ctrl+shift + F 2.跳转代码行ctrl+L 3.可以列出所有快捷键  Ctrl+Shift+L

  3. HDOJ/HDU 1804 Deli Deli(英语单词复数形式~)

    Problem Description Mrs. Deli is running the delicatessen store "Deli Deli". Last year Mrs ...

  4. Windows下,通过程序设置全屏抗锯齿(多重采样)的方法

    这里说的全屏抗锯齿,不是基于着色器的FXAA之类的方式,而是兼容性更好的,基于固定管线的多重采样方式. 先来说一下开发环境,我用的是VC2013+GLEW1.11. 要通过程序设置多重采样,首先需要进 ...

  5. 批处理at命令--一切尽在计划中

    让计算机在自己规定的时间里干自己规定的事,一切尽在计划之中.所以at命令你一定不能错过. 概述 列出在指定的时间和日期在计算机上运行的已计划命令或计划命令和程序,以及设置在指定时间和日期在计算机上运行 ...

  6. 批量Linux 网络安装环境建立工具cobbler/kickstart

    批量Linux 网络安装环境建立工具网络安装服务器套件:     Cobbler(Red Hat 2008年发布的项目)    Kickstart(Red Hat08年前项目,相关脚本令人望而却步,现 ...

  7. 关于css命名规范

    1 newsHeader-logo,第一个单词小写,第二个单词大写,第三个单词加-

  8. 在JSP页面中调用另一个JSP页面中的变量

    在jsp学习中,经常需要在一个jsp页面中调用另一个jsp页面中的变量,下面就这几天的学习,总结一下. jsp页面之间的变量调用有多种方法: 1.通过jsp的内置对象—request对象获取参数: ( ...

  9. dig命令 安装

    获取容器 dns 信息 需要安装dig 命令 yum install bind-utils

  10. N!末尾有多少个零

    题目一:210!最后结果有几个零. 请自己思索10分钟以上再看解释 凡是这种题目必有规律可言, 关键是你找到这个规律的恒心.可采用笨拙的方法思考. 1!  =  1                   ...