C# 多线程入门系列(二)

简介: C# 多线程入门系列(二)

线程(英语: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等待任意线程。

备注

以上就是多线程的基础知识,旨在抛砖引玉,一起学习,共同进步。

古从军行【作者】李颀 【朝代】唐

白日登山望烽火,黄昏饮马傍交河。行人刁斗风沙暗,公主琵琶幽怨多。

野云万里无城郭,雨雪纷纷连大漠。胡雁哀鸣夜夜飞,胡儿眼泪双双落。

闻道玉门犹被遮,应将性命逐轻车。年年战骨埋荒外,空见蒲桃入汉家。

相关文章
|
1月前
|
开发框架 .NET API
RESTful API 设计与实现:C# 开发者的一分钟入门
【10月更文挑战第5天】本文从零开始,介绍了如何使用 C# 和 ASP.NET Core 设计并实现一个简单的 RESTful API。首先解释了 RESTful API 的概念及其核心原则,然后详细说明了设计 RESTful API 的关键步骤,包括资源识别、URI 设计、HTTP 方法选择、状态码使用和错误处理。最后,通过一个用户管理 API 的示例,演示了如何创建项目、定义模型、实现控制器及运行测试,帮助读者掌握 RESTful API 的开发技巧。
54 7
|
23天前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
1月前
|
C#
C#入门
C#入门
26 0
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
13天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
29 1
|
1月前
|
算法 NoSQL Java
Springboot3新特性:GraalVM Native Image Support和虚拟线程(从入门到精通)
这篇文章介绍了Spring Boot 3中GraalVM Native Image Support的新特性,提供了将Spring Boot Web项目转换为可执行文件的步骤,并探讨了虚拟线程在Spring Boot中的使用,包括如何配置和启动虚拟线程支持。
77 9
Springboot3新特性:GraalVM Native Image Support和虚拟线程(从入门到精通)
|
20天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
43 3
|
21天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
4天前
|
程序员 C# 图形学
全面的C#/.NET自学入门指南
全面的C#/.NET自学入门指南
|
1月前
|
存储 消息中间件 NoSQL
Redis 入门 - C#.NET Core客户端库六种选择
Redis 入门 - C#.NET Core客户端库六种选择
59 8