C#多线程学习之Thread、ThreadPool、Task、Parallel四者区别

目录
  • Thread
  • ThreadPool
  • Task
  • Parallel
  • Task专讲

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述多线程的发展历程【Thread,ThreadPool,Task,Parallel】,仅供学习分享使用,如有不足之处,还请指正。

Thread

Thread做为早期【.Net Framework1.0】的.Net提供的多线程方案,提供了很多的封装方法,来操作线程。具体如下所示:

  1. Start方法,用于启动一个线程。线程的状态变更为Running。
  2. Suspend方法,挂起一个线程,或者如果线程状态为已挂起,则不起作用。
  3. Resume方法,如果线程为已挂起,这继续运行。
  4. Join方法,等待线程,直到线程结束,也可以设置等待时间。
  5. Abort方法,强制终止线程,跑出ThreadAbortException异常。
  6. 其他线程属性:IsBackground是否后台线程,IsThreadPoolThread是否线程池线程,IsAlive线程是否运行,Priority线程优先级,ThreadState当前线程状态,ManagedThreadId线程唯一标识等

通过Thread可以单独的开启一个线程,通过构造函数来创建线程对象,可以是无参数也可以是带参数。其中参数ThreadStart是一个无参数委托,ParameterizedThreadStart为一个带参数委托。

无参数,示例如下所示:

private void btnThread_Click(object sender, EventArgs e)
{
    ThreadStart threadStart = new ThreadStart(DoSomethingLong);
    Thread thread = new Thread(threadStart);
    thread.Start();
}

private void DoSomethingLong() {
    string name = "Thread";
    Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
    //CPU计算累加和
    long rest = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        rest += i;
    }
    Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);
}

示例结果如下所示:

带参数示例,如下所示:

private void btnThread2_Click(object sender, EventArgs e)
{
    ParameterizedThreadStart threadStart = new ParameterizedThreadStart(DoSomethingLongWithParam);
    Thread thread = new Thread(threadStart);
    string name = "Param";
    thread.Start(name);
}

private void DoSomethingLongWithParam(object name) {
    Console.WriteLine("************DoSomethingLongWithParam 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
    //CPU计算累加和
    long rest = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        rest += i;
    }
    Console.WriteLine("************DoSomethingLongWithParam 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);
}

带参数示例结果,如下所示:

通过对以上示例进行分析,得出结论如下所示:

  1. 线程是由操作系统进行创建的,Thread提供的方法只是对底层方法的封装。比如执行对应方法后,操作系统并不会立即执行相应的操作,而要CPU时间片轮转后才会执行响应。
  2. Thread创建线程过于松散,缺乏管理,例如,如果同时创建10000个线程,程序也不会报错,但是系统可能无法承载如此多的线程而导致崩溃。
  3. Thread的频繁创建和销毁,也会消耗系统资源。

ThreadPool

为了应对Thread创建缺乏管理的问题,在后续版本【.Net Framework2.0】中推出了线程池的概念。那什么是线程池呢?

池化资源管理设计思想:线程是一种资源,之前每次需要线程,都是去创建线程,使用完成后,再释放掉。池化,就是做一个容器,容器提前申请指定数量的线程,需要用到线程的时候,直接到线程池中取,用完之后再放回容器【通过控制状态标识线程是否正在被使用】。避免频繁的创建和销毁,容器还会根据限制的数量取申请和释放。

关于通过线程池创建多线程,具体示例如下:

private void btnThread3_Click(object sender, EventArgs e)
 {
    WaitCallback waitCallback = new WaitCallback(DoSomethingLongWithParam);
     string name = "ThreadPool";
     ThreadPool.QueueUserWorkItem(waitCallback,name);
 }

线程池示例执行结果如下所示:

通过对线程池执行结果进行分析,得出结论如下:

  1. 两次执行结果,均为同一个线程ID,说明线程用完并未销毁,而是放回线程池子,待下次使用时重新取出,继续使用。
  2. 通过线程池可以有效的控制线程并发的数量,避免资源的浪费。
  3. 通过分析源码发现,ThreadPool线程池提供的接口较少,在线程等待和交互方面不太友好。

Task

随着.Net版本的演化,后续版本【.Net Framework3.0】推出了Task做为多线程解决方案。默认情况下,可以通过构造函数创建Task,示例如下:

private void btnTask_Click(object sender, EventArgs e)
 {
     Action action = new Action(DoSomethingLong);
     Task task = new Task(action);
     task.Start();
 }

默认Task示例,执行结果如下:

通过对以上Task示例和源码进行分析,得出结论如下:

  1. Task产生的线程,全部都是线程池线程。
  2. Task提供的丰富的API,便于开发实践。

Parallel

Parallel提供对并行线程的支持,可以通过Parallel同时发起多个线程,在某些方面具有应用优势,默认示例如下所示:

 private void btnParallel_Click(object sender, EventArgs e)
 {
     Action action = new Action(DoSomethingLong);
     Parallel.Invoke(action,action,action);
 }

Parallel的Invoke方法执行,结果如下:

通过对Parallel的Invoke示例方法进行分析,得出结论如下:

  1. Parallel的Invoke方法,可以同时开启多个线程,同时主线程【线程ID=1】也会参与计算,即页面也会卡住。
  2. Parallel可以通过ParallelOptions.MaxDegreeOfParallelism指定并发数量。

Task专讲

以下面的一个场景为例进行说明:

假如开发一个系统,流程如下:

1. 前期的需求调研,需求分析,系统设计,详细设计(顺序执行,是开发编码的前提)

2.按模块开发【中间阶段,可多人同时工作】

3.测试【顺序执行,是开发编码的后续工作】

分析:以上三个阶段,每一个阶段又可以细分数个小阶段,其中有些阶段是顺序执行的,有些阶段又可以并行执行。

以代码的形式进行描述,如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    //开发前的工作
    Console.WriteLine("组建团队");
    Console.WriteLine("需求分析");
    Console.WriteLine("系统设计");
    Console.WriteLine("详细设计");
    //开始开发
    Task.Run(() => { Coding("张三", "接口"); });
    Task.Run(() => { Coding("李四", "前端页面"); });
    Task.Run(() => { Coding("王五", "手机App"); });
    Task.Run(() => { Coding("刘大", "后端业务"); });
    //开发后的工作
    Console.WriteLine("alpha测试");
    Console.WriteLine("beta测试");
    Console.WriteLine("uat测试");
    Console.WriteLine("系统上线");
}

private void Coding(string developer,string model) {
    Console.WriteLine("【Begin】在{0},{1}开始开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer,model, Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000);
    Console.WriteLine("【 End 】在{0},{1}完成开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer, model, Thread.CurrentThread.ManagedThreadId);
}

示例运行结果,如下所示:

通过分析以上示例,发现程序并未按照预期的运行,很明显的一点:测试跑到了开发前面。

为了解决上述顺序错乱的问题,Task提供了WaitAll方法,如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    //开发前的工作
    Console.WriteLine("组建团队");
    Console.WriteLine("需求分析");
    Console.WriteLine("系统设计");
    Console.WriteLine("详细设计");
    //开始开发
    List<Task> tasks = new List<Task>();
    tasks.Add(Task.Run(() => { Coding("张三", "接口"); }));
    tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); }));
    tasks.Add(Task.Run(() => { Coding("王五", "手机App"); }));
    tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); }));
    Task.WaitAll(tasks.ToArray());
    //开发后的工作
    Console.WriteLine("alpha测试");
    Console.WriteLine("beta测试");
    Console.WriteLine("uat测试");
    Console.WriteLine("系统上线");
}

运行示例,结果如下所示:

通过运行以上示例,发现:顺序确实符合预期,可以满足要求,但是程序会卡住,这点不太友好。

如何才能优雅的控制先后顺序呢?Task还提供了TaskFactory,如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    //开发前的工作
    Console.WriteLine("组建团队");
    Console.WriteLine("需求分析");
    Console.WriteLine("系统设计");
    Console.WriteLine("详细设计");
    //开始开发
    List<Task> tasks = new List<Task>();
    tasks.Add(Task.Run(() => { Coding("张三", "接口"); }));
    tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); }));
    tasks.Add(Task.Run(() => { Coding("王五", "手机App"); }));
    tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); }));
    TaskFactory taskFactory = new TaskFactory();
    taskFactory.ContinueWhenAll(tasks.ToArray(), new Action<Task[]>((taskArray) => {
        //开发后的工作
        Console.WriteLine("alpha测试");
        Console.WriteLine("beta测试");
        Console.WriteLine("uat测试");
        Console.WriteLine("系统上线");
    }));

}

TaskFactory示例测试,如下所示:

通过对示例进行分析,得出如下结论:

  1. 业务逻辑上已要求,页面也不会卡顿,优雅的实现了多线程的操作。
  2. Task产生的线程,为线程池线程。
  3. TaskFactory不仅提供了ContinueWhenAll等待所有线程,还提供了ContinueWhenAny等待任意线程。

以上就是C#多线程学习之Thread、ThreadPool、Task、Parallel四者区别的详细内容,更多关于C# 多线程的资料请关注我们其它相关文章!

(0)

相关推荐

  • C# 并行和多线程编程——Task进阶知识

    一.Task的嵌套 Task中还可以再嵌套Task,Thread中能不能这样做,我只能说我是没这样写过.Task中的嵌套,我感觉其实也可以分开来写,不过嵌套起来会方便管理一点.Task中的嵌套分为两种,关联嵌套和非关联嵌套,就是说内层的Task和外层的Task是否有联系,下面我们编写代码先来看一下非关联嵌套,及内层Task和外层Task没有任何关系,还是在控制台程序下面,代码如下: static void Main(string[] args) { var pTask = Task.Factor

  • C#多线程Thread使用示例详解

    本文实例为大家分享了C#多线程Thread使用的示例代码,供大家参考,具体内容如下 多线程: 线程生命周期状态图: C#线程优先级(概率高低): 基本使用示例: using System; using System.Threading; namespace month_9_10._1009 { class Run5 { /* 测试线程的调用过程 * 主线程输出world,子线程输出hello */ public static void showHello() { for(int i = 0; i

  • c# 并行和多线程编程——认识Parallel

    随着多核时代的到来,并行开发越来越展示出它的强大威力!使用并行程序,充分的利用系统资源,提高程序的性能.在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Tasks.这里面有很多关于并行开发的东西,今天第一篇就介绍下最基础,最简单的--认识和使用Parallel. 一. Parallel的使用 在Parallel下面有三个常用的方法invoke,For和ForEach. 1.Parallel.Invoke 这是最简单,最简洁的将串行的代码并行化. 在这里先

  • C# 多线程学习之基础入门

    目录 同步方式 异步多线程方式 异步多线程优化 异步回调 异步信号量 异步多线程返回值 异步多线程返回值回调 线程(英语:thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.进程是资源分配的基本单位.所有与该进程有关的资源,都被记录在进程控制块PCB中.以表示该进程拥有这些资源或正在使用它们.本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成

  • C#多线程ThreadPool线程池详解

    简单说明一下: 线程池可以看做容纳线程的容器:一个应用程序最多只能有一个线程池:ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池: 每排入一个工作函数,就相当于请求创建一个线程: 线程池的作用: 1.线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率. 2.如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜.),况且我们还不能控制线程池中线程的开始.挂起.和

  • c# Parallel类的使用

    Parallel类是对线程的抽象,提供数据与任务的并行性.类定义了静态方法For和ForEach,使用多个任务来完成多个作业.Parallel.For和Parallel.ForEach方法在每次迭代的时候调用相同的代码,而Parallel.Invoke()方法允许同时调用不同的方法.Parallel.ForEach()方法用于数据的并行性,Parallel.Invoke()方法用于任务的并行性. 1.For()方法 For()方法用于多次执行一个任务,可以并行运行迭代,但迭代的顺序并没指定.Fo

  • C#多线程学习之Thread、ThreadPool、Task、Parallel四者区别

    目录 Thread ThreadPool Task Parallel Task专讲 线程(英语:thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.进程是资源分配的基本单位.所有与该进程有关的资源,都被记录在进程控制块PCB中.以表示该进程拥有这些资源或正在使用它们.本文以一些简单的小例子,简述多线程的发展历程[Thread,ThreadPool,Task

  • Java 多线程学习详细总结

    目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1

  • 详解C#中 Thread,Task,Async/Await,IAsyncResult的那些事儿

    说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们 1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部分可以同时执行:对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行,这样主线程就不会阻塞,可以继续往下执行:等到后台线程执行完毕,再通知主线程,然后做出对应操作! 在C#中开启新线程比较简单 static void Main(string[]

  • 完全解析Android多线程中线程池ThreadPool的原理和使用

    前言对于多线程,大家应该很熟悉.但是,大家了解线程池吗?今天,我将带大家全部学习关于线程池的所有知识. 目录 1. 简介 2. 工作原理 2.1 核心参数线程池中有6个核心参数,具体如下 上述6个参数的配置 决定了 线程池的功能,具体设置时机 = 创建 线程池类对象时 传入 ThreadPoolExecutor类 = 线程池的真正实现类 开发者可根据不同需求 配置核心参数,从而实现自定义线程池 // 创建线程池对象如下 // 通过 构造方法 配置核心参数 Executor executor =

  • C#多线程之线程池(ThreadPool)

    一.简介 前面介绍了平时用到的大多数的多线程的例子,但在实际开发中使用的线程往往是大量的和更为复杂的,这时,每次都创建线程.启动线程.从性能上来讲,这样做并不理想(因为每使用一个线程就要创建一个,需要占用系统开销):从操作上来讲,每次都要启动,比较麻烦.为此引入的线程池的概念. 好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”. 在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.需要处理的

  • C#多线程之线程池ThreadPool详解

    一.ThreadPool概述 提供一个线程池,该线程池可用于执行任务.发送工作项.处理异步 I/O.代表其他线程等待以及处理计时器. 创建线程需要时间.如果有不同的小任务要完成,就可以事先创建许多线程/在应完成这些任务时发出请求.不需要自己创建这样一个列表.该列表由ThreadPool类托管. 这个类会在需要时增减池中线程的线程数,直到最大的线程数.池中的最大线程数是可配置的.在双核CPU中,默认设置为1023 个工作线程和1000个I/O线程.也可以指定在创建线程池时应立即启动的最小线程数,以

  • .Net中Task Parallel Library的基本用法

    我们知道,每个应用程序就是一个进程,一个进程有多个线程.Task Parallel Library为我们的异步编程.多线程编程提供了强有力的支持,它允许一个主线程运行的同时,另外的一些线程或Task也同时运行.本篇体验基本用法. 基本用法 Taks的构造函数接收的类型是Action,也就是一个委托. static void Main(string[] args) { var t1 = new Task(() => { Console.WriteLine("任务1开始"); Thr

  • C#多线程学习之(四)使用线程池进行多线程的自动管理

    本文实例讲述了C#多线程学习之使用线程池进行多线程的自动管理.分享给大家供大家参考.具体如下: 在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒 这一般使用Timer(定时器)来解决: ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系

  • C#多线程学习之(一)多线程的相关概念分析

    本文详细分析了C#多线程学习之多线程的相关概念.分享给大家供大家参考.具体分析如下: 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源. 而一个进程又是由多个线程所组成的. 什么是线程? 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数. 什么是多线程? 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序

随机推荐