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

【从零开始入门unity游戏开发之——C#篇37】进程、线程和C# 中实现多线程有多种方案

文章目录

  • 进程、线程和C#多线程
  • 一、`进程`的基本概念
  • 二、`线程`的基本概念
  • 三、C#中的多线程
    • 1、为什么需要多线程?
    • 2、*C# 中如何实现多线程**
      • 2.1 **使用 `Thread` 类**
        • (1)示例
        • (2)线程休眠
        • (3)设置为后台线程
          • 普通的前台线程
          • 设置为后台线程
        • (4)停止线程
      • 2.2 **使用 `Task` 类** (推荐)
      • 2.3 使用 ThreadPool 类
      • 2.4 **使用 `async` 和 `await`**
      • 2.5 使用 Parallel 类
    • 3、对比总结:
    • 4、锁(`lock`)线程安全
      • 4.1 问题分析
      • 4.2 解决方案
      • 4.3 示例:
      • 4.4 工作机制:
    • 5、死锁
      • 5.1 问题分析
      • 5.2 避免死锁的策略:
  • 四、总结
  • 专栏推荐
  • 完结

进程、线程和C#多线程

一、进程的基本概念

进程是计算机中正在运行的程序的实例。每个进程有自己的内存空间和资源,系统通过进程来隔离不同程序之间的执行。

例如,你打开一个浏览器,它会启动一个进程;然后,你打开一个文本编辑器,它又是另一个进程。每个进程相互独立,它们有自己的内存、文件句柄等。

我们打开win的任务管理器,这里其实就是一个个进程,一个正在运行的程序就是一个进程
在这里插入图片描述

二、线程的基本概念

线程是进程中的一个执行单元。一个进程至少有一个线程,这个线程负责执行进程中的代码。

如果你有一个程序,这个程序会启动一个主线程来执行代码。当程序需要执行多个任务时,可以在这个进程里创建多个线程。

线程是 CPU 调度的基本单位。你可以把线程理解成进程内部的工作小单元,多个线程可以在同一个进程中并行或交替执行。

三、C#中的多线程

C# 支持多线程编程,这意味着你可以同时执行多个任务或操作。举个简单的例子,你可以在一个程序里同时播放音乐、下载文件、处理数据,而这些操作并不会互相干扰。每个操作都可以由不同的线程处理。

1、为什么需要多线程?

假设你有一个程序需要做很多事情(比如下载文件、处理数据等),如果它只用一个线程,程序会在一个任务完成后才开始下一个任务,这会导致程序的响应变慢,特别是在处理耗时任务时。多线程的好处就是能并行或并发地处理这些任务,让程序更高效。

2、C# 中如何实现多线程*

在 C# 中实现多线程有多种方案,每种方案有不同的使用场景和特点。以下是几种常见的实现方式:

2.1 使用 Thread

Thread 类提供了直接的多线程控制,是最基本的多线程编程方式。

  • 优点:简单直接,易于理解。
  • 缺点:需要手动管理线程的生命周期,容易产生线程安全问题,效率较低。
(1)示例
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 创建一个线程,执行 DoWork 方法
        Thread t = new Thread(DoWork);
        t.Start(); // 启动线程

        // 主线程继续执行其他操作
        Console.WriteLine("Main thread is running.");
    }

    static void DoWork()
    {
        // 线程执行的任务
        Console.WriteLine("Working in the background...");
    }
}

在上面的例子中,我们创建了一个新的线程 t,并让它去执行 DoWork 方法。然后主线程会继续执行后面的代码。主线程和新线程是并行工作的。

(2)线程休眠

可以通过 Thread.Sleep() 方法让线程暂停执行指定的时间。

  • 参数是毫秒数,1秒=1000毫秒。

示例代码:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread t = new Thread(NewThreadLogic);
        t.Start();
    }

    static void NewThreadLogic()
    {
        while (true)
        {
            Console.WriteLine("线程执行中...");
            Thread.Sleep(1000);  // 休眠1秒
        }
    }
}
(3)设置为后台线程

使用 Thread.IsBackground = true 将线程设置为后台线程,可以让你处理那些不需要程序等待完成的任务,提高程序的响应速度。

在 后台线程 的情况下,主线程结束时,后台线程会被强制终止,即使它还没有完成任务。

普通的前台线程
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 创建后台线程
        Thread t = new Thread(DoWork);
        t.Start();
        
        Console.WriteLine("主线程结束,后台线程继续执行中...");
    }

    static void DoWork()
    {
        int count = 0;
        while (count < 5)
        {
            Console.WriteLine($"工作线程执行中... {count}");
            Thread.Sleep(1000);  // 每秒执行一次
            count++;
        }
    }
}

结果,主线程结束时,如果有后台线程在运行,程序就不会退出,直到后台线程执行完毕。
在这里插入图片描述

设置为后台线程
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 创建后台线程
        Thread t = new Thread(DoWork);
        t.IsBackground = true;  // 设置为后台线程
        t.Start();

        // 主线程不等待后台线程
        Console.WriteLine("主线程结束,后台线程继续执行中...");
        // 主线程结束,程序直接退出
    }

    static void DoWork()
    {
        int count = 0;
        while (count < 5)
        {
            Console.WriteLine($"工作线程执行中... {count}");
            Thread.Sleep(1000);  // 每秒执行一次
            count++;
        }
    }
}

结果,在 后台线程 的情况下,主线程结束时,后台线程会被强制终止,即使它还没有完成任务。
在这里插入图片描述

(4)停止线程

对于死循环线程,必须通过某种方式来终止它。可以使用一个标志变量来控制线程停止。
也可以调用 Thread.Abort() 来强制终止线程,但该方法在 .NET Core 中已经被废弃,因为它会抛出 ThreadAbortException 异常,且有潜在的稳定性风险。所以这里就不介绍了。

使用标志变量停止线程

using System;
using System.Threading;

class Program
{
    static bool isRunning = true;

    static void Main()
    {
        Thread t = new Thread(NewThreadLogic);
        t.Start();

        // 稍后停止线程
        Thread.Sleep(5000);  // 等待5秒
        isRunning = false;   // 设置标志变量为false,线程会退出
    }

    static void NewThreadLogic()
    {
        while (isRunning)
        {
            Console.WriteLine("线程正在运行");
            Thread.Sleep(1000);  // 休眠1秒
        }
        Console.WriteLine("线程已停止");
    }
}

2.2 使用 Task (推荐)

除了 Thread 类,C# 还提供了 Task 类,它更高级、使用起来更简单,通常用于处理异步操作。Task 适合于执行一些后台工作,不需要创建管理线程。示例如下:

  • 优点:Task 自动管理线程池线程,提供了更高层次的抽象,支持任务组合、取消、异常处理等功能。
  • 缺点:由于是基于线程池,因此任务是异步执行的,可能不是实时响应的。
using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Task.Run(() => DoWork());
        Console.ReadLine(); // 防止程序过早退出
    }

    static void DoWork()
    {
        Console.WriteLine("Task is running...");
    }
}

2.3 使用 ThreadPool 类

ThreadPool 是 .NET 提供的一个线程池,可以重用线程池中的线程,从而减少线程的创建和销毁开销。

  • 优点:线程池可以重用线程,避免了线程的频繁创建和销毁,效率较高。
  • 缺点:线程池的线程是共享的,可能会引发竞态条件(Race Condition),需要特别注意线程安全。
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWork);
    }

    static void DoWork(object state)
    {
        Console.WriteLine("ThreadPool thread is running...");
    }
}

2.4 使用 asyncawait

C# 中 async 和 await 关键字使得异步编程变得更加简单,虽然它并不直接涉及多线程,但可以实现非阻塞的操作,常用于 I/O 密集型任务(如网络请求、文件操作等)。

  • 优点:代码结构清晰,不需要显式创建线程,可以轻松处理异步操作。
  • 缺点:适用于 I/O 密集型操作,对于 CPU 密集型任务效果不佳。
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // 异步执行方法
        await DoWorkAsync();

        Console.WriteLine("Main thread is running.");
    }

    static async Task DoWorkAsync()
    {
        // 模拟耗时任务
        await Task.Delay(2000); // 延迟2秒
        Console.WriteLine("Work completed in the background.");
    }
}

2.5 使用 Parallel 类

Parallel 类是并行编程的高级 API,通常用于对大量数据进行并行处理。它可以自动分配任务到多个线程,适用于大规模数据处理等场景。

  • 优点:简化并行任务的实现,自动管理线程和任务分配。
  • 缺点:适用于需要大量并行计算的场景,对于简单的任务可能显得过于复杂。
using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.For(0, 10, i =>
        {
            Console.WriteLine($"Task {i} is running...");
        });
    }
}

3、对比总结:

方案描述优点缺点实际应用示例
Thread使用 System.Threading.Thread 类直接创建和管理线程。控制灵活,可以创建、启动和管理线程;可以访问线程状态等。线程创建和销毁开销较大;较低级别,需要手动管理线程生命周期。执行长时间运算或独立任务,如后台下载或复杂计算任务。
Task使用 System.Threading.Tasks.Task 类进行任务管理,支持异步编程。更高层次的抽象;自动管理线程池;支持异步编程;异常处理更简洁。相较于 Thread,无法精细控制线程;适合短时间任务。网络请求、文件操作等 I/O 密集型操作;并行计算等。
ThreadPool使用 ThreadPool 提供的线程池进行线程管理。重用线程,减少线程创建销毁的开销;自动管理线程;适合短时间任务。不能精确控制线程的生命周期;不适合需要大量 CPU 时间的任务。高并发任务、简单的后台操作、事件处理等。
async/await基于异步编程模型,使用 asyncawait 关键字进行异步调用。简化异步编程;不会阻塞线程;适合 I/O 密集型操作。只能用于 I/O 密集型任务;不适用于 CPU 密集型任务。异步文件读取、数据库访问、Web API 请求等 I/O 密集型操作。
Parallel使用 System.Threading.Tasks.Parallel 类来执行并行操作。简化并行计算;自动分配任务到线程池中的线程;易于实现并行。无法控制线程的数量和执行方式;仅适合 CPU 密集型任务。大规模数据处理(例如数组排序、并行计算等)。

4、锁(lock)线程安全

4.1 问题分析

多个线程同时访问共享数据时,可能会导致数据冲突或者不一致。(比如线程1想修改a变量,线程2又想获取a变量。)

4.2 解决方案

因此,在多线程编程中,要特别注意线程安全。常用的做法是使用锁(lock)来避免同时访问共享资源。

4.3 示例:

using System;
using System.Threading;

class Program
{
    static object obj = new object();  // 用于加锁的对象

    static void Main()
    {
        Thread t1 = new Thread(ThreadLogic);
        Thread t2 = new Thread(ThreadLogic);
        
        t1.Start();
        t2.Start();
    }

    static void ThreadLogic()
    {
        while (true)
        {
            lock (obj)  // 锁定共享资源
            {
                //操作共享数据
            }
            Thread.Sleep(1000);  // 休眠1秒
        }
    }
}

lock 锁定的对象通常是一个引用类型(例如 object)。通常情况下,最好选择一个私有的、专用的对象作为锁对象,而不是使用诸如 this 或者 typeof(MyClass) 这样的公共对象。这样做的原因是防止外部代码错误地修改锁对象,导致不安全的同步行为。

4.4 工作机制:

  • 进入锁:当线程执行 lock (obj) 时,它会尝试获得 obj的锁。

    • 如果没有其他线程持有该锁,当前线程就能进入锁定的代码块。
    • 如果其他线程已经持有该锁,当前线程将被阻塞,直到锁被释放。
  • 释放锁:当线程执行完 lock 代码块中的代码后,自动释放锁,使得其他等待的线程可以获得锁并进入临界区。

5、死锁

5.1 问题分析

如果程序中存在多个线程互相等待对方释放锁的情况,可能会发生死锁。比如线程 A 持有锁 1,等待锁 2,线程 B 持有锁 2,等待锁 1,这种情况会导致两个线程永远无法继续执行。

5.2 避免死锁的策略:

  • 尽量避免多个锁的嵌套。
  • 遵循一致的锁定顺序:如果多个线程需要获取多个锁,应该遵循相同的顺序获取锁,以防止死锁。

四、总结

  1. 进程是一个正在运行的程序,它有自己的内存和资源。
  2. 线程是进程中的执行单位,一个进程至少有一个线程,多个线程可以在同一个进程中并行工作。
  3. 在 C# 中,多线程可以通过 Thread 类、Task 类以及 async/await 来实现。
  4. 需要注意线程安全问题,避免并发访问导致数据冲突。

专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.kler.cn/a/458213.html

相关文章:

  • 基于 LangChain 实现数据库问答机器人
  • AI 自动化编程对编程教育的影响
  • 太通透了,Android 流程分析 蓝牙enable流程(stack/hidl)
  • uniapp3 手写签名组件(vue3 语法)封装与应用
  • win11 vs2022 opencv 4.10 camshift示例程序运行
  • 【Unity】 HTFramework框架(五十七)通过Tag、Layer批量搜索物体
  • Linux arm 编译安装glibc-2.29
  • C语言学习笔记(3)
  • 【hackmyvm】soul靶机wp
  • vue在action中调用action的函数
  • 如何限制软件访问文件范围,阻止越权访问
  • UE5改变物体坐标轴位置
  • Vscode连接InternStudio进行debug
  • 从编译到电路:Verilog的“黑魔法“转换过程
  • 租赁小程序的优势与应用场景分析
  • 【JDBC】转账案例
  • KNN分类算法 HNUST【数据分析技术】(2025)
  • 探索PyTorch:从入门到实践的demo全解析
  • CES Asia 2025优惠期倒计时5天,科技盛宴即将开启
  • 如何在IDEA一个窗口中导入多个项目
  • 关于 VRRP的详解
  • 爬虫代理服务要怎么挑选?
  • Spring Security3.0.2版本
  • 计算机网络技术研究方向有哪些创新点
  • 华为电源工程师面试题
  • 基于物联网的园区停车管理系统的设计与实现