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

c# .NET core多线程的详细讲解

在 .NET Core 中,多线程是实现并发执行任务的核心技术,可有效提升程序对 CPU 和 I/O 资源的利用率(如同时处理多个网络请求、并行计算等)。.NET Core 提供了丰富的多线程 API,从基础的 Thread 类到现代的 Task/async/await,再到并行计算库,覆盖了不同场景的需求。本文将从概念、核心 API、线程安全到实际应用,详细讲解 .NET Core 多线程编程。

一、多线程基础概念

  • 线程(Thread):操作系统调度的最小单位,负责执行程序中的指令。一个进程(如一个 .NET 程序)可包含多个线程,共享进程的内存空间,但拥有独立的栈和寄存器。
  • 并发(Concurrency):多个任务在同一时间段内交替执行(如单 CPU 核心通过快速切换线程实现“同时”执行)。
  • 并行(Parallelism):多个任务在同一时刻真正同时执行(依赖多 CPU 核心,每个核心执行一个线程)。
  • 多线程的目的
    • 提高 CPU 利用率(尤其 CPU 密集型任务,如数据分析、复杂计算);
    • 避免单线程阻塞(尤其 I/O 密集型任务,如文件读写、网络请求时,线程无需等待 I/O 完成)。

二、.NET Core 多线程核心 API

.NET Core 提供了多层次的多线程 API,从底层的线程控制到高层的任务抽象,选择需结合场景(如任务类型、复杂度、是否需要返回值等)。

1. System.Threading.Thread:基础线程控制

Thread 类是最底层的线程 API,直接操作操作系统线程,适合需要细粒度控制线程(如优先级、前台/后台线程)的场景。

基本用法
using System;
using System.Threading;class ThreadDemo
{static void Main(){// 1. 创建线程:传入线程执行的方法(无参数)Thread thread1 = new Thread(PrintNumbers);// 2. 启动线程thread1.Start();// 3. 创建带参数的线程(参数必须是 object 类型)Thread thread2 = new Thread(PrintMessage);thread2.Start("Hello from thread2");  // 传递参数// 主线程执行Console.WriteLine("Main thread is running...");// 4. 等待线程执行完成(阻塞主线程)thread1.Join();  // 等待 thread1 结束thread2.Join();  // 等待 thread2 结束Console.WriteLine("All threads completed.");}// 线程执行的方法(无参数)static void PrintNumbers(){for (int i = 1; i <= 5; i++){Console.WriteLine($"Thread1: {i}");Thread.Sleep(100);  // 模拟工作(暂停 100ms,释放 CPU 给其他线程)}}// 线程执行的方法(带参数)static void PrintMessage(object message){string msg = message as string ?? "No message";for (int i = 1; i <= 3; i++){Console.WriteLine($"Thread2: {msg} - {i}");Thread.Sleep(150);}}
}

输出(顺序可能因线程调度不同而变化)

Main thread is running...
Thread1: 1
Thread2: Hello from thread2 - 1
Thread1: 2
Thread2: Hello from thread2 - 2
Thread1: 3
Thread1: 4
Thread2: Hello from thread2 - 3
Thread1: 5
All threads completed.
关键属性与方法
  • IsBackground:设置为 true 时,线程为后台线程(进程退出时自动终止,如控制台程序主线程结束后,后台线程会被强制终止);默认 false 为前台线程(进程需等待所有前台线程结束才退出)。
    thread1.IsBackground = true;  // 设为后台线程
    
  • Priority:设置线程优先级(Lowest/BelowNormal/Normal/AboveNormal/Highest),操作系统会优先调度高优先级线程(但不保证,受系统调度算法影响)。
  • Abort():终止线程(已过时,可能导致资源泄漏,建议通过 CancellationToken 优雅终止)。
  • Join(int millisecondsTimeout):等待线程完成,最多等待指定毫秒数。
优缺点
  • 优点:直接控制线程,适合需要设置优先级、前台/后台属性的场景。
  • 缺点
    • 线程创建/销毁开销大(每个线程默认栈大小 1MB,且操作系统调度成本高);
    • 不适合大量短期任务(如同时处理 1000 个请求,创建 1000 个线程会耗尽资源);
    • 无内置返回值支持(需手动通过共享变量获取结果)。
2. ThreadPool:线程池管理

线程池是 .NET 维护的线程集合,用于复用线程(避免频繁创建/销毁),适合大量短期任务(如 Web 服务器处理请求、小计算任务)。线程池自动管理线程数量(默认最小 1 个,最大根据 CPU 核心动态调整)。

基本用法
using System;
using System.Threading;class ThreadPoolDemo
{static void Main(){// 1. 向线程池提交任务(无返回值)ThreadPool.QueueUserWorkItem(PrintNumbers);  // 无参数// 2. 提交带参数的任务ThreadPool.QueueUserWorkItem(PrintMessage, "Hello from ThreadPool");  // 参数作为第二个参数// 主线程等待(避免进程提前退出,因为线程池线程是后台线程)Console.WriteLine("Main thread waiting...");Thread.Sleep(2000);  // 等待足够长时间让线程池任务执行Console.WriteLine("Main thread exit.");}static void PrintNumbers(object? state)  // 线程池任务方法必须接受 object? 参数{for (int i = 1; i <= 5; i++){Console.WriteLine($"ThreadPool Task1: {i}");Thread.Sleep(100);}}static void PrintMessage(object? state){string msg = state as string ?? "No message";for (int i = 1; i <= 3; i++){Console.WriteLine($"ThreadPool Task2: {msg} - {i}");Thread.Sleep(150);}}
}
关键特性
  • 后台线程:线程池中的线程都是后台线程,进程退出时会被终止。
  • 线程复用:任务完成后,线程不会销毁,而是回到线程池等待新任务,减少开销。
  • 最大线程数控制:可通过 ThreadPool.SetMaxThreads 调整最大工作线程数和 I/O 完成端口线程数(通常无需手动调整,.NET 会自动优化)。
    // 获取当前线程池设置
    ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
    Console.WriteLine($"Max worker threads: {workerThreads}, Max completion threads: {completionPortThreads}");// 设置最大工作线程数(示例:设为 100)
    ThreadPool.SetMaxThreads(100, completionPortThreads);
    
优缺点
  • 优点:减少线程创建开销,适合大量短期任务,自动管理线程数量。
  • 缺点
    • 无法设置线程优先级(所有线程池线程优先级为 Normal);
    • 无法直接获取任务返回值(需手动处理);
    • 不适合长时间运行的任务(会占用线程池资源,导致其他任务等待)。
3. TaskTask<TResult>:基于任务的异步编程(TAP)

Task 是 .NET Core 推荐的现代异步编程模型(TAP:Task-based Asynchronous Pattern),抽象了线程的细节,专注于“任务”的执行和结果。Task 无返回值,Task<TResult> 有返回值,可通过线程池或单独线程执行。

基本用法(创建与等待任务)
using System;
using System.Threading.Tasks;class TaskDemo
{static async Task Main()  // 注意:Main 方法可声明为 async Task{// 1. 创建并启动任务(无返回值):Task.Run 会将任务放入线程池Task task1 = Task.Run(() => {for (int i = 1; i <= 5; i++){Console.WriteLine($"Task1: {i}");Task.Delay(100).Wait();  // 模拟工作(类似 Thread.Sleep,但返回 Task)}});// 2. 创建带返回值的任务(Task<int>)Task<int> task2 = Task.Run(() => {int sum = 0;for (int i = 1; i <= 5; i++){sum += i;Console.WriteLine($"Task2: Adding {i}, sum={sum}");Task.Delay(150).Wait();}return sum;  // 返回结果});// 3. 等待任务完成(多种方式)// 3.1 同步等待(阻塞当前线程)task1.Wait();  // 等待 task1 完成// 3.2 异步等待(不阻塞当前线程,释放 CPU 给其他任务)int result = await task2;  // await 是关键:当前方法暂停,等待 task2 完成后再继续Console.WriteLine($"Task2 结果:{result}");  // 输出:15(1+2+3+4+5)// 4. 同时等待多个任务Task task3 = Task.Run(() => Console.WriteLine("Task3 完成"));Task task4 = Task.Run(() => Console.WriteLine("Task4 完成"));await Task.WhenAll(task3, task4);  // 等待所有任务完成Console.WriteLine("Task3 和 Task4 都已完成");}
}
async/await 语法(核心)

async/await 是简化 Task 编程的语法糖,让异步代码看起来像同步代码,避免“回调地狱”。

  • async:标记方法为异步方法,返回类型必须是 void(不推荐)、TaskTask<TResult>
  • await:暂停当前方法执行,等待 Task 完成后再继续,期间不阻塞线程(线程可去执行其他任务)。

示例:I/O 密集型任务(文件读写)

using System;
using System.IO;
using System.Threading.Tasks;class AsyncAwaitDemo
{static async Task Main(){Console.WriteLine("开始读取文件...");string content = await ReadFileAsync("test.txt");  // 异步等待文件读取完成Console.WriteLine($"文件内容:{content}");}// 异步读取文件(I/O 密集型任务,适合用 async/await)static async Task<string> ReadFileAsync(string path){// 使用 File.ReadAllTextAsync(返回 Task<string>),并用 await 等待return await File.ReadAllTextAsync(path);  // 不阻塞线程,I/O 完成后自动回调}
}

为什么 I/O 密集型任务适合 async/await
I/O 操作(如文件读写、网络请求)时,CPU 处于空闲状态(等待硬件响应)。async/await 会释放当前线程去处理其他任务,I/O 完成后再通过线程池线程继续执行后续代码,大幅提高线程利用率。

任务延续(ContinueWith

任务完成后自动执行后续操作(无需显式等待):

Task<int> task = Task.Run(() => 
{Console.WriteLine("任务执行中...");return 100;
});// 任务完成后执行延续操作
task.ContinueWith(t => 
{Console.WriteLine($"任务结果:{t.Result},延续操作执行");
});// 等待所有操作完成
task.Wait();
优缺点
  • 优点
    • 抽象线程细节,专注任务逻辑;
    • 支持返回值、异常处理、任务延续;
    • async/await 简化异步代码,避免回调嵌套;
    • 高效利用线程(尤其 I/O 密集型任务)。
  • 缺点:学习曲线较陡,需理解任务状态(Running/RanToCompletion/Faulted 等)。
4. Parallel 类:数据并行计算

Parallel 类用于数据并行(将数据分成多个部分,并行处理),适合 CPU 密集型任务(如大规模数据计算),自动利用多核 CPU 提高效率。

基本用法(Parallel.ForParallel.ForEach
using System;
using System.Threading.Tasks;class ParallelDemo
{static void Main(){// 1. Parallel.For:并行循环(类似 for 循环)Console.WriteLine("Parallel.For 开始...");Parallel.For(0, 10, i =>  // 从 0 到 10(不包含 10),并行执行每个 i{Console.WriteLine($"For 循环:i={i},线程 ID={Task.CurrentId}");Task.Delay(100).Wait();  // 模拟计算});Console.WriteLine("Parallel.For 结束\n");// 2. Parallel.ForEach:并行遍历集合(类似 foreach)int[] numbers = { 1, 2, 3, 4, 5 };Console.WriteLine("Parallel.ForEach 开始...");Parallel.ForEach(numbers, num =>  // 并行处理 numbers 中的每个元素{int square = num * num;Console.WriteLine($"ForEach:{num} 的平方是 {square},线程 ID={Task.CurrentId}");Task.Delay(150).Wait();});Console.WriteLine("Parallel.ForEach 结束");}
}

输出特点:循环/遍历的顺序是无序的(多个线程同时执行),线程 ID 可能重复(线程池复用线程)。

取消并行操作(CancellationToken
using System;
using System.Threading;
using System.Threading.Tasks;class ParallelCancellationDemo
{static void Main(){CancellationTokenSource cts = new CancellationTokenSource();// 启动一个任务,300ms 后取消并行操作Task.Run(() => {Task.Delay(300).Wait();cts.Cancel();  // 发送取消信号Console.WriteLine("已发送取消信号");});try{// 并行循环,支持取消Parallel.For(0, 100, new ParallelOptions{CancellationToken = cts.Token,  // 传入取消令牌MaxDegreeOfParallelism = 4  // 最大并行度(同时运行的线程数)}, i => {Console.WriteLine($"处理 i={i}");Task.Delay(100).Wait();  // 模拟计算cts.Token.ThrowIfCancellationRequested();  // 检查是否取消});}catch (OperationCanceledException){Console.WriteLine("并行操作已取消");}}
}
优缺点
  • 优点:自动利用多核 CPU,简化并行循环代码,适合 CPU 密集型任务。
  • 缺点
    • 不适合依赖顺序的任务(并行执行顺序无序);
    • 过度并行(如 MaxDegreeOfParallelism 过大)会导致线程切换开销增加,反而降低性能。
5. PLINQ(Parallel LINQ)

PLINQ 是 LINQ 的并行版本,通过 AsParallel() 将查询转换为并行执行,自动利用多核处理大数据集。

基本用法
using System;
using System.Linq;class PlinqDemo
{static void Main(){// 生成 10000 个随机数int[] numbers = Enumerable.Range(1, 10000).Select(_ => new Random().Next(1, 1000)).ToArray();// 普通 LINQ(单线程):筛选偶数并计算平方和var normalQuery = numbers.Where(n => n % 2 == 0).Select(n => n * n).Sum();Console.WriteLine($"普通 LINQ 结果:{normalQuery}");// PLINQ(并行):通过 AsParallel() 启用并行var parallelQuery = numbers.AsParallel()  // 转换为并行查询.Where(n => n % 2 == 0).Select(n => n * n).Sum();Console.WriteLine($"PLINQ 结果:{parallelQuery}");  // 结果与普通 LINQ 相同(计算逻辑一致)}
}
关键操作
  • AsParallel():将序列转换为并行查询。
  • WithDegreeOfParallelism(n):限制并行度(最多 n 个线程)。
  • AsOrdered():保持查询结果的顺序(默认并行查询结果无序)。

三、线程安全:避免竞态条件

多线程共享数据时,若多个线程同时读写同一资源,可能导致竞态条件(数据不一致)。.NET Core 提供了多种线程同步机制保证线程安全。

1. lock 语句(最常用)

lock 确保同一时间只有一个线程执行代码块,本质是对 Monitor.EnterMonitor.Exit 的封装。

using System;
using System.Threading.Tasks;class LockDemo
{private static int _count = 0;private static readonly object _lockObj = new object();  // 锁对象(必须是引用类型,且唯一)static void Main(){// 10 个线程同时递增 _countTask[] tasks = new Task[10];for (int i = 0; i < 10; i++){tasks[i] = Task.Run(IncrementCount);}Task.WaitAll(tasks);Console.WriteLine($"最终 count 值:{_count}");  // 若不加锁,结果可能小于 10000;加锁后一定是 10000}static void IncrementCount(){for (int i = 0; i < 1000; i++){lock (_lockObj)  // 同一时间只有一个线程进入此代码块{_count++;  // 临界区:对共享资源的操作}}}
}

注意

  • 锁对象必须是 static readonly(避免被修改或回收);
  • 锁的范围应尽可能小(只包裹临界区),减少线程阻塞时间。
2. Interlocked 类:原子操作

Interlocked 提供原子操作(不可中断的操作),适合简单的数值增减、赋值等,性能优于 lock

using System;
using System.Threading;
using System.Threading.Tasks;class InterlockedDemo
{private static int _count = 0;static void Main(){Task[] tasks = new Task[10];for (int i = 0; i < 10; i++){tasks[i] = Task.Run(IncrementCount);}Task.WaitAll(tasks);Console.WriteLine($"最终 count 值:{_count}");  // 10000}static void IncrementCount(){for (int i = 0; i < 1000; i++){Interlocked.Increment(ref _count);  // 原子递增(线程安全)}}
}

常用方法:Increment(递增)、Decrement(递减)、Add(相加)、Exchange(赋值)、CompareExchange(比较并交换)。

3. 并发集合(System.Collections.Concurrent

为多线程设计的集合类,无需手动加锁即可安全访问,如:

  • ConcurrentDictionary<TKey, TValue>:线程安全的字典;
  • ConcurrentQueue<T>:线程安全的队列;
  • ConcurrentStack<T>:线程安全的栈。
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;class ConcurrentCollectionDemo
{static void Main(){// 线程安全的字典ConcurrentDictionary<int, string> dict = new ConcurrentDictionary<int, string>();// 10 个线程同时添加键值对Parallel.For(0, 10, i => {// TryAdd:原子操作,添加成功返回 truebool added = dict.TryAdd(i, $"Value_{i}");Console.WriteLine($"添加 {i}: {added}");});// 遍历结果foreach (var item in dict){Console.WriteLine($"{item.Key}: {item.Value}");}}
}

四、多线程适用场景与最佳实践

1. 适用场景
  • CPU 密集型任务:如数据分析、数学计算、图像处理等,适合用 ParallelPLINQTask.Run(利用多核并行计算)。
  • I/O 密集型任务:如文件读写、数据库操作、网络请求等,适合用 async/await(不阻塞线程,提高利用率)。
2. 最佳实践
  • 优先使用 Task/async/await:现代 .NET 开发的推荐方式,兼顾性能和代码可读性。
  • 避免创建大量线程:优先使用线程池或 Task,减少线程创建开销。
  • 最小化锁范围lock 只包裹必要的临界区,避免线程长时间阻塞。
  • 使用并发集合:多线程操作集合时,优先用 ConcurrentDictionary 等,避免手动加锁。
  • 避免 Thread.Sleep:改用 Task.Delay(非阻塞,释放线程)。
  • 正确处理异常Task 异常需通过 awaitTask.Wait() 捕获,否则可能导致程序崩溃。

总结

.NET Core 多线程编程提供了从底层 Thread 到高层 Task/async/await 的完整 API 体系:

  • 简单短期任务:用 ThreadPoolTask.Run
  • I/O 密集型任务:用 async/await 最大化线程利用率;
  • CPU 密集型并行计算:用 ParallelPLINQ
  • 细粒度线程控制:用 Thread 类(谨慎使用)。

核心是根据任务类型选择合适的 API,并通过 lockInterlocked 或并发集合保证线程安全,最终实现高效、稳定的并发程序。

http://www.dtcms.com/a/498742.html

相关文章:

  • Python机器学习---2.算法:逻辑回归
  • solidity的变量学习小结
  • 【Java 开发日记】MySQL 与 Redis 如何保证双写一致性?
  • 基于知识图谱(Neo4j)和大语言模型(LLM)的图检索增强(GraphRAG)的台风灾害知识问答系统(vue+flask+AI算法)
  • 短剧APP开发性能优化专项:首屏加载提速技术拆解
  • 2025年远程控制软件横评:UU远程、ToDesk、向日葵
  • 前端核心理论深度解析:从基础到实践的关键知识点
  • 合肥官方网站建设有哪些公司
  • 大模型-高效优化技术全景解析:微调 量化 剪枝 梯度裁剪与蒸馏 下
  • 微信个人号开发中如何高效实现API二次开发
  • 网页设计与网站建设实战大全wordpress文章页实现图片幻灯展现
  • Ubuntu22.04 VMware虚拟机文件拖放问题:文字复制正常但文件拖放失效
  • Vue Router 路由守卫钩子详解
  • 开源 Linux 服务器与中间件(三)服务器--Nginx
  • Java 大视界 -- Java 大数据在智能农业无人机植保作业路径规划与药效评估中的应用
  • 【OpenGL】模板测试(StencilTest)
  • 文本描述驱动的可视化工具在IDE中的应用与实践
  • C#程序实现将MySQL的存储过程转换成Oracle的存储过程
  • IDEA 中 Tomcat 部署 Java Web 项目
  • 全景网站模版校园微网站建设方案ppt模板
  • 东莞公司网站建设公司哪家好制作网站链接
  • 【Linux】Socket编程UDP
  • “桌面自动化”解救“浏览器自动化”受阻(反爬虫检测)(pywinauto、pyautogui、playwright)
  • 线程安全集合源码速读:Hashtable、Vector、Collections.synchronizedMap
  • 大文件上传与文件下载
  • React Native 项目中 WebSocket 的完整实现方案
  • 电脑建设银行怎样设置网站查询密码手机网站建设价钱是多少
  • Linux内核ida数据结构使用
  • SAP MM委外采购订单执行报表分享
  • Docker中授权普通用户使用docker命令以及解决无权限访问/var/run/docker.sock错误