当前位置: 首页 > news >正文

【C#进阶】C# 多线程

序号系列文章
19【C#进阶】C# 集合类
20【C#进阶】C# 泛型
21【C#进阶】C# 匿名方法

文章目录

  • 前言
  • 1、线程与多线程的基本概念
  • 2、创建并使用线程
  • 3、检索线程对象
  • 4、前台线程和后台线程
  • 5、Thread 类的属性和方法
  • 结语

前言

🐪 hello大家好,我是哈桑c,本文为大家介绍 C# 中的多线程。


1、线程与多线程的基本概念

线程是操作系统能够进行运算调度的最小单位,它被包含在进程1之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流,一个进程可以并发2多个线程,每个线程并行执行不同的任务。

多线程是指在软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,进而提升整体处理性能的能力。

在 C# 中,实现多线程技术最常使用的类就是包含在 System.Threading 命名空间中的 Thread 类。Thread 类是一个定义创建和控制线程,设置其优先级并获取其状态的类。以一个简单的程序演示 Thread 类的用法,借此讨论多线程的使用。

代码示例:

using System;
using System.Threading;

// 简单的线程场景:启动一个静态方法运行在第二个线程上。
public class ThreadExample
{
    // 被运行的子线程
    public static void ThreadProc()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("子线程: {0}", i);
            Thread.Sleep(0);
        }
    }

    public static void Main()
    {
        Console.WriteLine("主线程:开启第二个线程。");
        
        Thread t = new Thread(new ThreadStart(ThreadProc));
        t.Start();      // 开启子线程的工作
        
        // 同时开启主线程的工作
        for (int i = 0; i < 4; i++)
        {
            Console.WriteLine("主线程:开始工作");
            Thread.Sleep(0);
        }

        Console.WriteLine("主线程:调用Join(),等待子线程结束。");
        t.Join();
        Console.WriteLine("主线程:子线程已经 Join 回来了。按Enter键结束程序。");
        Console.ReadLine();
    }
}

运行结果:
在这里插入图片描述
在上例中可以看出,使用 Thread 类我们不仅可以执行主线程3的内容,还可以创建新的 Thread 对象以此来运行子线程4的内容。这样我们就成功实现了一个多线程程序。

2、创建并使用线程

在 Thread 类中,创建子线程可以通过 Thread t = new Thread(); 调用构造方法的方式来实现。扩展的 Thread 类调用 Start() 方法来开始子线程的执行。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class Example
{
    public static void Main()
    {
        var th = new Thread(ExecuteInForeground);
        th.Start(4500);
        Thread.Sleep(1000);
        Console.WriteLine("主线程 ({0}) 等待...",
                          Thread.CurrentThread.ManagedThreadId);
    }

    private static void ExecuteInForeground(Object obj)
    {
        int interval;
        try
        {
            interval = (int)obj;
        }
        catch (InvalidCastException)
        {
            interval = 5000;
        }

        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 {0}: {1}, 属性 {2}",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        {
            Console.WriteLine("线程 {0}: 运行 {1:N2} 描述",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
        } while (sw.ElapsedMilliseconds <= interval);
        sw.Stop();
    }
}

运行结果:
在这里插入图片描述
在上例中,我们使用 var th = new Thread(ExecuteInForeground); 的方法并传入了 ExecuteInForeground 的构造方法名成功的创建了一个子线程
在这里插入图片描述
同时我们也可以使用 Thread 类中 start 方法来启动子线程的运行。注意如果方法需要有参数的话,那么这时就需要在 start 方法上传入参数,如果没有则反之。
在这里插入图片描述

3、检索线程对象

在使用 Thread 类时,如果想要获取线程的信息,可以从正在执行的代码中使用 Thread 类的属性来检索当前正在运行的线程的引用对象

代码示例:

using System;
using System.Threading;

public class ExampleThread
{
    // 创建一个obj对象便于用于锁机制
    static Object obj = new Object();

    public static void Main()
    {
        ThreadPool.QueueUserWorkItem(ShowThreadInformation);    // 将方法排入队列以便执行。 
        var th1 = new Thread(ShowThreadInformation);
        th1.Start();
        var th2 = new Thread(ShowThreadInformation);
        th2.IsBackground = true;
        th2.Start();
        Thread.Sleep(500);
        ShowThreadInformation(null);
    }

    private static void ShowThreadInformation(Object state)
    {
        lock (obj)      // 为保证线程之间的执行顺序,在这里加上一个锁
        {
            var th = Thread.CurrentThread;
            Console.WriteLine("托管线程 #{0}: ", th.ManagedThreadId);
            Console.WriteLine("后台线程: {0}", th.IsBackground);
            Console.WriteLine("线程池线程: {0}", th.IsThreadPoolThread);
            Console.WriteLine("优先级: {0}", th.Priority);
            Console.WriteLine("文化: {0}", th.CurrentCulture.Name);
            Console.WriteLine("UI文化: {0}", th.CurrentUICulture.Name);
            Console.WriteLine();
        }
    }
}

运行结果:
在这里插入图片描述
从上例中可以看到,我们不仅创建了托管线程,还创建了前台线程、后台线程以及线程池的对象。在这里我们使用了 ManagedThreadId 等属性输出了线程的 Id 值、是否为后台线程或线程池5、优先级和线程名等线程信息。
在这里插入图片描述

4、前台线程和后台线程

在 .NET 框架的公用语言运行时(Common Language Runtime,CLR)中能区分两种不同类型的线程:前台线程和后台线程。

前台线程和后台线程的区别:

  • 如果所有前台线程已终止,后台线程不会使进程保持运行。
  • 停止所有前台线程后,运行时将停止所有后台线程并关闭。

前台线程和后台线程分别的应用范围。

默认情况下,以下线程在前台执行:

  • 主线程: 常见的主应用程序线程均为前台线程。
  • 子线程: 通过调用类构造函数创建 Thread 的所有线程。

默认情况下,以下线程在后台执行:

  • 线程池线程: 由运行时维护的工作线程池。 可以使用 类配置线程池并计划线程池线程 ThreadPool 上的工作。
  • 非托管到托管的线程: 从非托管代码进入托管执行环境的所有线程。

在 Thread 类中,可以通过设置 IsBackground 的属性来更改在后台执行的线程。后台线程适用于只要应用程序正在运行就应继续执行但不阻止应用程序终止的任何操作,例如监视文件系统更改或传入套接字连接。

代码示例:

using System;
using System.Diagnostics;
using System.Threading;

public class BackgroundThreadExample
{
    public static void Main()
    {
        var th = new Thread(ExecuteInForeground);
        th.IsBackground = true;
        th.Start();
        Thread.Sleep(1000);
        Console.WriteLine("主线程 ({0}) 等待...", 
                        Thread.CurrentThread.ManagedThreadId); 
    }

    private static void ExecuteInForeground()
    {
        var sw = Stopwatch.StartNew();
        Console.WriteLine("线程 {0}: {1}, 优先级 {2}",
                          Thread.CurrentThread.ManagedThreadId,
                          Thread.CurrentThread.ThreadState,
                          Thread.CurrentThread.Priority);
        do
        {
            Console.WriteLine("Thread {0}: Elapsed {1:N2} seconds",
                              Thread.CurrentThread.ManagedThreadId,
                              sw.ElapsedMilliseconds / 1000.0);
            Thread.Sleep(500);
        } while (sw.ElapsedMilliseconds <= 5000);
        sw.Stop();
    }
}

运行结果:
在这里插入图片描述
从上例中可以看出,我们使用了 Thread 类对象的 IsBackground 属性将 th 线程对象设置为了后台线程。不像前面的示例一样 th 对象可以顺利执行不超过五秒的内容,在上例中可以看到 th 对象只执行了 0.51 秒(并不固定)。这是因为停止所有前台线程后,运行时将停止所有后台线程并关闭。所以在主线程(前台线程)输出"主线程 (1) 等待…"之后就表示主线程停止了,后台线程也会停止并关闭,并不会执行不超过五秒的内容。
在这里插入图片描述

5、Thread 类的属性和方法

以一个表格展示 Thread 类常用的属性和方法。

Thread 类常用的属性:

属性名描述
ApartmentState获取或设置此线程的单元状态。
CurrentCulture获取或设置当前线程的区域性。
CurrentPrincipal获取或设置线程的当前负责人(对基于角色的安全性而言)。
CurrentThread获取当前正在运行的线程。
CurrentUICulture获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。
ExecutionContext获取 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive获取指示当前线程的执行状态的值。
IsBackground获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread获取指示线程是否属于托管线程池的值。
ManagedThreadId获取当前托管线程的唯一标识符。
Name获取或设置线程的名称。
Priority获取或设置指示线程的调度优先级的值。
ThreadState获取一个值,该值包含当前线程的状态。

Thread 类常用的方法:

方法名描述
Abort()在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。 调用此方法通常会终止线程。
Equals(Object)确定指定对象是否等于当前对象。(继承自 Object)
Finalize()确保垃圾回收器回收 Thread 对象时释放资源并执行其他清理操作。
GetApartmentState()返回表示单元状态的 ApartmentState 值。
GetCompressedStack()返回 CompressedStack 对象,此对象可用于获取当前线程的堆栈。
GetCurrentProcessorId()获取用于指示当前线程正在哪个处理器上执行的 ID。
GetDomain()返回当前线程正在其中运行的当前域。
GetDomainID()返回唯一的应用程序域标识符。
GetHashCode()返回当前线程的哈希代码。
GetType()获取当前实例的 Type。(继承自 Object)
Interrupt()中断处于 WaitSleepJoin 线程状态的线程。
Join()在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。
MemberwiseClone()创建当前 Object 的浅表副本。(继承自 Object)
ResetAbort()取消当前线程所请求的 Abort(Object)。
Resume()继续已挂起的线程。
SetApartmentState(ApartmentState)在线程启动前设置其单元状态。
SetCompressedStack(CompressedStack)将捕获的 CompressedStack 应用到当前线程。
Sleep(Int32)将当前线程挂起指定的毫秒数。
SpinWait(Int32)导致线程等待由 iterations 参数定义的时间量。
Start()导致操作系统将当前实例的状态更改为 Running。
ToString()返回表示当前对象的字符串。(继承自 Object)
TrySetApartmentState(ApartmentState)在线程启动前设置其单元状态。
UnsafeStart()导致操作系统将当前实例的状态更改为 Running。
VolatileRead(Byte)向字段中读取值。
VolatileWrite(Byte, Byte)向字段中写入值。

点击了解更多多线程的使用。


结语

🐆 以上就是 C# 多线程的介绍啦,希望对大家有所帮助。感谢大家的支持。


  1. 进程: 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。 ↩︎

  2. 并发: 指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 ↩︎

  3. 主线程: 进程中第一个被执行的线程称为主线程。 ↩︎

  4. 子线程: 除了主线程外,在 C# 中使用 Thread t = new Thread(); 方式创建的线程均为子线程。 ↩︎

  5. 线程池: 是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 ↩︎

相关文章:

  • mlq移动最小二乘方法
  • 【Spring6】| Spring IoC注解式开发
  • 数据库+加密算法参考材料-2023.3.29
  • fastp软件介绍
  • 别再光靠工资过日子,外国程序员教你如何通过副业赚钱
  • 海心沙元宇宙音乐会虚拟主持人玩法再升级,虚拟动力技术全程助力
  • spark通过connector的方式读写starrocks
  • python实战应用讲解-【numpy专题篇】实用小技巧(六)(附python示例代码)
  • chatGPT陪你读源码
  • 37了解高可用技术方案,如冗余、容灾
  • 细思极恐,第三方跟踪器正在获取你的数据,如何防范?
  • 如何同时处理多个聊天
  • 一些idea操作
  • 【Java 并发编程】一文读懂线程、协程、守护线程
  • C++/MFC工程[4]——绘制直线段
  • 07.Android之多媒体问题
  • Angular6错误 Service: No provider for Renderer2
  • CODING 缺陷管理功能正式开始公测
  • DataBase in Android
  • ESLint简单操作
  • PHP CLI应用的调试原理
  • Transformer-XL: Unleashing the Potential of Attention Models
  • 初识 webpack
  • 搞机器学习要哪些技能
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 聚类分析——Kmeans
  • 每天一个设计模式之命令模式
  • 你不可错过的前端面试题(一)
  • 前端存储 - localStorage
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 原生JS动态加载JS、CSS文件及代码脚本
  • NLPIR智能语义技术让大数据挖掘更简单
  • Spring第一个helloWorld
  • 整理一些计算机基础知识!
  • ![CDATA[ ]] 是什么东东
  • #FPGA(基础知识)
  • (6)STL算法之转换
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (十六)串口UART
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (学习日记)2024.01.09
  • (一)kafka实战——kafka源码编译启动
  • (转)h264中avc和flv数据的解析
  • (转)平衡树
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET 分布式技术比较
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .NET和.COM和.CN域名区别
  • @我的前任是个极品 微博分析
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)
  • [Android]Android开发入门之HelloWorld