C# 线程与并发编程完全指南:从基础到高级带详细注释版(一篇读懂)

在 C# 开发中,线程与并发编程是提升程序性能、响应速度的核心技术,尤其适用于 UI 后台任务、批量处理、网络请求等场景。本文将系统梳理 C# 线程相关的所有核心知识点 ,包括基础概念、线程创建、同步机制、线程通信、线程池、Task 异步编程、高级工具类等,为所有代码添加详细注释,让你不仅能直接运行,还能理解每一步的原理,让你一篇文章吃透 C# 并发编程。

一、核心概念:线程与并发基础

在学习具体 API 前,先理清核心概念,避免理解偏差:

1. 线程与进程的关系

  • 进程:操作系统分配资源的基本单位(如一个.exe 程序),进程间资源隔离(内存、文件句柄等)。

  • 线程:进程内的执行单元,一个进程可包含多个线程,线程共享进程资源(如堆内存),但拥有独立的栈内存。

  • 核心优势:多线程可让程序 "同时" 执行多个任务(如 UI 线程响应点击,后台线程下载文件),提升吞吐量和用户体验。

2. 线程的生命周期

C# 线程的生命周期完全遵循操作系统线程模型,关键阶段如下:

阶段 说明
新建(New) 创建 Thread 对象,但未调用 Start(),线程未被操作系统调度。
就绪(Runnable) 调用 Start() 后,线程等待 CPU 调度(此时已被操作系统纳入线程队列)。
运行(Running) CPU 分配时间片,线程执行 ThreadStart 委托中的逻辑。
阻塞(Blocked) 线程因等待资源暂停(如 SleepWait、IO 操作),释放 CPU 时间片。
终止(Terminated) 线程执行完毕、抛出未捕获异常,或被正常取消(非强制终止)。

3. 线程的分类

  • 前台线程:默认类型,进程必须等待所有前台线程完成后才会退出(即使主线程结束)。

  • 后台线程 :通过 Thread.IsBackground = true 设置,进程无需等待后台线程完成,主线程结束后后台线程会被强制终止。

4. 并发与并行的区别

  • 并发(Concurrency):多个线程 "交替" 使用 CPU(单核 CPU 也能实现),看似同时执行(如单 CPU 下的多任务切换)。

  • 并行(Parallelism):多个线程 "同时" 执行(需多核 CPU 支持),真正的同时处理多个任务。

二、基础实现:Thread 类(传统线程)

Thread 类是 C# 最底层的线程操作 API,位于 System.Threading 命名空间,适合需要完全控制线程的场景(如设置优先级、后台线程)。

1. 线程的创建与启动

C# 中创建线程的核心是通过 ThreadStartParameterizedThreadStart 委托指定线程执行逻辑,支持 3 种常用方式:

方式 1:无参数线程(ThreadStart 委托)
cs 复制代码
using System;
using System.Threading;
​
class ThreadDemo1
{
    // 主线程入口
    static void Main()
    {
        // 1. 创建 Thread 对象,传入无参数的线程执行逻辑(ThreadStart 委托)
        // DoWork 是线程要执行的方法,无需参数
        Thread thread = new Thread(DoWork);
        
        // 2. 可选配置:设置为后台线程
        // 后台线程特性:主线程结束后,后台线程会被强制终止(避免程序挂起)
        thread.IsBackground = true;
        
        // 3. 给线程命名(用于调试,在 VS 调试窗口可看到线程名称)
        thread.Name = "无参数工作线程";
        
        // 4. 启动线程:线程进入「就绪状态」,等待 CPU 调度
        // 注意:Start() 只是触发调度,不会阻塞主线程
        thread.Start();
​
        // 主线程的逻辑(与子线程并行执行)
        Console.WriteLine($"主线程({Thread.CurrentThread.Name})执行中...");
        
        // 5. 主线程等待子线程完成(Join() 会阻塞主线程)
        // 若不调用 Join(),主线程可能提前退出,导致后台线程被终止
        thread.Join();
        
        Console.WriteLine("主线程执行完毕");
    }
​
    /// <summary>
    /// 无参数的线程执行方法(ThreadStart 委托对应的方法签名)
    /// </summary>
    static void DoWork()
    {
        // Thread.CurrentThread:获取当前正在执行的线程实例
        Console.WriteLine($"子线程({Thread.CurrentThread.Name})开始执行");
        
        // Thread.Sleep(2000):线程休眠 2000 毫秒(2 秒)
        // 特性:释放 CPU 时间片,让其他线程执行,但不释放已持有的锁
        Thread.Sleep(2000);
        
        Console.WriteLine($"子线程({Thread.CurrentThread.Name})执行完毕");
    }
}
方式 2:带参数线程(ParameterizedThreadStart 委托)

参数必须是 object 类型,需手动装箱拆箱:

cs 复制代码
using System;
using System.Threading;
​
class ThreadDemo2
{
    static void Main()
    {
        // 1. 创建 Thread 对象,传入带参数的执行逻辑(ParameterizedThreadStart 委托)
        // DoWorkWithParam 方法必须接收 object 类型参数
        Thread thread = new Thread(DoWorkWithParam);
        
        thread.Name = "带参数工作线程";
        thread.IsBackground = true;
        
        // 2. 启动线程并传递参数:参数类型必须是 object(装箱)
        thread.Start("这是传递给子线程的参数");
        
        // 主线程等待子线程完成
        thread.Join();
        
        Console.WriteLine("主线程执行完毕");
    }
​
    /// <summary>
    /// 带参数的线程执行方法(ParameterizedThreadStart 委托对应的签名)
    /// </summary>
    /// <param name="obj">接收的参数(object 类型,需手动拆箱)</param>
    static void DoWorkWithParam(object obj)
    {
        // 拆箱:将 object 类型转为目标类型(此处是 string)
        // 注意:若传递的参数类型与目标类型不匹配,会抛出 InvalidCastException
        string param = (string)obj;
        
        Console.WriteLine($"子线程({Thread.CurrentThread.Name})接收参数:{param}");
        
        // 模拟任务耗时 1 秒
        Thread.Sleep(1000);
        
        Console.WriteLine($"子线程({Thread.CurrentThread.Name})执行完毕");
    }
}
方式 3:Lambda 表达式(简洁写法)

无需单独定义方法,适合简单逻辑:

cs 复制代码
using System;
using System.Threading;
​
class ThreadDemo3
{
    static void Main()
    {
        // 1. 无参数 Lambda 线程
        Thread thread1 = new Thread(() =>
        {
            // Lambda 表达式直接包含线程逻辑,无需单独定义方法
            Console.WriteLine($"线程 1({Thread.CurrentThread.ManagedThreadId}):Lambda 无参数线程");
            Thread.Sleep(800); // 模拟耗时
        });
        thread1.Name = "Lambda无参线程";
        thread1.IsBackground = true;
        thread1.Start();
​
        // 2. 带参数 Lambda 线程(通过闭包传递参数,无需装箱拆箱)
        string message = "Lambda 闭包参数"; // 闭包变量:子线程可访问主线程的局部变量
        Thread thread2 = new Thread(() =>
        {
            // 注意:闭包变量的生命周期会被延长,直到线程执行完毕
            Console.WriteLine($"线程 2({Thread.CurrentThread.ManagedThreadId}):接收闭包参数:{message}");
            Thread.Sleep(1200);
        });
        thread2.Name = "Lambda带参线程";
        thread2.IsBackground = true;
        thread2.Start();
​
        // 等待两个线程都完成
        thread1.Join();
        thread2.Join();
        
        Console.WriteLine("所有子线程执行完毕,主线程退出");
    }
}

2. Thread 类的关键方法与属性

方法 / 属性 作用说明
Start() 启动线程,进入就绪状态(仅能调用一次)。
Join() 阻塞当前线程,等待目标线程执行完毕。
Join(int milliseconds) 超时等待(毫秒),超时后当前线程继续执行。
Sleep(int milliseconds) 线程休眠指定时间(毫秒),释放 CPU 时间片,不释放锁。
Yield() 主动放弃当前 CPU 时间片,让其他线程优先执行(仅在同优先级线程间)。
IsBackground 属性:设置是否为后台线程(默认 false,前台线程)。
Name 属性:设置线程名称(用于调试)。
Priority 属性:设置线程优先级(ThreadPriority.Lowest ~ Highest,默认 Normal)。
IsAlive 属性:判断线程是否处于运行 / 阻塞状态(未终止)。
Abort() 强制终止线程(已过时!会抛出 ThreadAbortException,可能导致资源泄露,禁止使用)。

3. 线程优先级的注意事项

  • 线程优先级仅为操作系统调度提供 "建议",不保证高优先级线程一定先执行。

  • 避免设置过高优先级(如 Highest),可能导致低优先级线程 "饥饿"(无法获取 CPU 时间)。

三、并发安全:同步机制(解决资源竞争)

多线程共享资源时(如全局变量、数据库连接),会出现 "指令交错" 导致数据不一致(如超卖、余额错误),这种问题称为竞态条件(Race Condition)。同步机制的核心是保证 "临界区"(共享资源的操作代码)同一时间只有一个线程执行。

C# 提供多种同步工具,按易用性和场景分类如下:

1. lock 关键字(最常用)

lock 是 C# 最简洁的同步方式,底层基于 Monitor 类,语法糖封装,推荐优先使用。

核心规则:
  • 锁对象必须是引用类型 (如 objectstring,但不推荐用 string 常量,可能被池化复用)。

  • 锁对象应设为 private static(静态资源)或 private(实例资源),避免被外部修改。

  • 临界区代码应尽量精简(仅包含共享资源操作),避免长时间阻塞。

示例:解决多线程累加问题
cs 复制代码
using System;
using System.Threading;
​
class LockDemo
{
    // 共享资源:多个线程会同时修改的变量(此处模拟计数器)
    private static int _count = 0;
    
    // 锁对象:用于控制临界区访问(必须是引用类型)
    // 1. 用 private static 修饰:静态资源对应静态锁对象,避免外部访问
    // 2. 不推荐用 string/this 作为锁对象:string 可能被池化复用,this 可能被外部引用
    private static readonly object _lockObj = new object();
​
    static void Main()
    {
        // 创建 10 个线程,同时执行累加操作
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];
        
        for (int i = 0; i < threadCount; i++)
        {
            // 每个线程都执行 Increment 方法(修改共享资源 _count)
            threads[i] = new Thread(Increment);
            threads[i].Name = $"累加线程{i + 1}";
            threads[i].IsBackground = true;
            threads[i].Start();
        }
​
        // 等待所有线程完成(避免主线程提前退出)
        foreach (var thread in threads)
        {
            thread.Join();
        }
​
        // 若不加锁,最终结果会小于 10000(因指令交错);加锁后正确输出 10000
        Console.WriteLine($"最终累加结果:{_count}");
    }
​
    /// <summary>
    /// 累加方法(修改共享资源 _count)
    /// </summary>
    static void Increment()
    {
        // 循环 1000 次,每次累加 1(模拟多次修改共享资源)
        for (int i = 0; i < 1000; i++)
        {
            // lock 关键字:保证临界区代码同一时间只有一个线程执行
            // 原理:进入 lock 时获取 _lockObj 的锁,退出时自动释放锁(即使抛异常)
            lock (_lockObj)
            {
                // 临界区:操作共享资源的代码(必须精简,减少锁持有时间)
                _count++; 
                // 注意:_count++ 实际是 3 步操作(读取→加1→写入),无锁时会交错
            }
            
            // 非临界区:无需同步的代码(尽量放在 lock 外部,提高并发效率)
            // Thread.Sleep(1); // 模拟其他耗时操作
        }
    }
}

2. Monitor 类(更灵活的锁)

Monitorlock 的底层实现,提供更精细的控制(如尝试获取锁、超时锁),语法比 lock 繁琐,但功能更强。

核心方法:
  • Enter(object obj):获取锁(若锁被占用,阻塞当前线程)。

  • TryEnter(object obj, int millisecondsTimeout):尝试获取锁,超时返回 false(非阻塞)。

  • Exit(object obj):释放锁(必须与 Enter 成对出现,建议在 finally 中调用)。

  • Pulse() / PulseAll():唤醒等待队列中的线程(用于线程通信)。

  • Wait():释放锁,进入等待队列(用于线程通信)。

示例:超时获取锁
cs 复制代码
using System;
using System.Threading;
​
class MonitorDemo
{
    private static int _count = 0;
    private static readonly object _lockObj = new object();
​
    static void Main()
    {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(IncrementWithMonitor);
            threads[i].Name = $"Monitor线程{i + 1}";
            threads[i].IsBackground = true;
            threads[i].Start();
        }
​
        foreach (var thread in threads)
        {
            thread.Join();
        }
​
        Console.WriteLine($"最终累加结果:{_count}");
    }
​
    /// <summary>
    /// 使用 Monitor 实现同步(支持超时获取锁)
    /// </summary>
    static void IncrementWithMonitor()
    {
        for (int i = 0; i < 1000; i++)
        {
            bool gotLock = false; // 标记是否成功获取锁
            try
            {
                // Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
                // 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
                // 优势:避免无限等待,适合高并发场景(防止死锁)
                gotLock = Monitor.TryEnter(_lockObj, 100);
                
                if (gotLock)
                {
                    // 成功获取锁,执行临界区代码
                    _count++;
                }
                else
                {
                    // 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
                    Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
                }
            }
            finally
            {
                // 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
                if (gotLock)
                {
                    Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
                }
            }
        }
    }
}

3. 其他同步工具(按场景选择)

3.1 SemaphoreSlim(信号量:控制并发线程数)

用于限制同时访问临界区的线程数量(如限流、连接池控制),轻量级,支持异步等待。

cs 复制代码
// 信号量:允许 3 个线程同时访问
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3);
​
static void Main()
{
    for (int i = 0; i < 10; i++)
    {
        new Thread(DoWorkWithSemaphore).Start(i);
    }
}
​
static void DoWorkWithSemaphore(object id)
{
    Console.WriteLine($"线程 {id} 等待进入临界区");
    _semaphore.Wait(); // 获取信号量(若已达 3 个,阻塞)
​
    try
    {
        Console.WriteLine($"线程 {id} 进入临界区,当前并发数:{3 - _semaphore.CurrentCount}");
        Thread.Sleep(1000); // 模拟耗时操作
    }
    finally
    {using System;
using System.Threading;
​
class MonitorDemo
{
    private static int _count = 0;
    private static readonly object _lockObj = new object();
​
    static void Main()
    {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(IncrementWithMonitor);
            threads[i].Name = $"Monitor线程{i + 1}";
            threads[i].IsBackground = true;
            threads[i].Start();
        }
​
        foreach (var thread in threads)
        {
            thread.Join();
        }
​
        Console.WriteLine($"最终累加结果:{_count}");
    }
​
    /// <summary>
    /// 使用 Monitor 实现同步(支持超时获取锁)
    /// </summary>
    static void IncrementWithMonitor()
    {
        for (int i = 0; i < 1000; i++)
        {
            bool gotLock = false; // 标记是否成功获取锁
            try
            {
                // Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
                // 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
                // 优势:避免无限等待,适合高并发场景(防止死锁)
                gotLock = Monitor.TryEnter(_lockObj, 100);
                
                if (gotLock)
                {
                    // 成功获取锁,执行临界区代码
                    _count++;
                }
                else
                {
                    // 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
                    Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
                }
            }
            finally
            {
                // 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
                if (gotLock)
                {
                    Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
                }
            }
        }
    }
}
        Console.WriteLine($"线程 {id} 退出临界区");
        _semaphore.Release(); // 释放信号量
    }
}
3.2 Mutex(互斥锁:跨进程同步)

lock 类似,但支持跨进程同步 (如多个程序访问同一文件),重量级,开销比 lock 大。

cs 复制代码
using System;
using System.Threading;
​
class MonitorDemo
{
    private static int _count = 0;
    private static readonly object _lockObj = new object();
​
    static void Main()
    {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(IncrementWithMonitor);
            threads[i].Name = $"Monitor线程{i + 1}";
            threads[i].IsBackground = true;
            threads[i].Start();
        }
​
        foreach (var thread in threads)
        {
            thread.Join();
        }
​
        Console.WriteLine($"最终累加结果:{_count}");
    }
​
    /// <summary>
    /// 使用 Monitor 实现同步(支持超时获取锁)
    /// </summary>
    static void IncrementWithMonitor()
    {
        for (int i = 0; i < 1000; i++)
        {
            bool gotLock = false; // 标记是否成功获取锁
            try
            {
                // Monitor.TryEnter:尝试获取锁,超时时间 100 毫秒
                // 参数 1:锁对象;参数 2:超时时间(毫秒);返回值:是否获取成功
                // 优势:避免无限等待,适合高并发场景(防止死锁)
                gotLock = Monitor.TryEnter(_lockObj, 100);
                
                if (gotLock)
                {
                    // 成功获取锁,执行临界区代码
                    _count++;
                }
                else
                {
                    // 获取锁超时,可执行降级逻辑(如记录日志、跳过本次操作)
                    Console.WriteLine($"线程 {Thread.CurrentThread.Name}:获取锁超时,跳过本次累加");
                }
            }
            finally
            {
                // 关键:只有成功获取锁,才需要释放(避免抛 SynchronizationLockException)
                if (gotLock)
                {
                    Monitor.Exit(_lockObj); // 释放锁(必须在 finally 中,确保锁一定会释放)
                }
            }
        }
    }
}
3.3 ReaderWriterLockSlim(读写锁:读多写少场景优化)

读操作共享,写操作排他,适合 "读多写少" 场景(如缓存查询、配置读取),比 lock 更高效。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Threading;
​
class ReaderWriterLockSlimDemo
{
    // 读写锁:替代 lock,优化读多写少场景
    private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    
    // 共享资源:模拟缓存字典(读多写少)
    private static readonly Dictionary<string, string> _cache = new Dictionary<string, string>();
​
    static void Main()
    {
        // 启动 5 个读线程(模拟高并发读)
        for (int i = 0; i < 5; i++)
        {
            int readerId = i;
            new Thread(() => ReadCache(readerId))
            {
                Name = $"读线程{readerId}",
                IsBackground = true
            }.Start();
        }
​
        // 启动 2 个写线程(模拟低频写)
        for (int i = 0; i < 2; i++)
        {
            int writerId = i;
            new Thread(() => WriteCache(writerId))
            {
                Name = $"写线程{writerId}",
                IsBackground = true
            }.Start();
        }
​
        // 等待所有线程完成
        Thread.Sleep(5000);
        _rwLock.Dispose(); // 释放读写锁资源
        Console.WriteLine("程序执行完毕");
    }
​
    /// <summary>
    /// 读操作(共享锁:多个线程可同时读)
    /// </summary>
    /// <param name="readerId">读线程ID</param>
    static void ReadCache(int readerId)
    {
        // 循环模拟多次读操作
        for (int i = 0; i < 3; i++)
        {
            // EnterReadLock:获取读锁(共享锁,多个线程可同时获取)
            _rwLock.EnterReadLock();
            try
            {
                // 读临界区:读取共享资源(缓存)
                Console.WriteLine($"读线程 {readerId}:缓存数据数 {_cache.Count}(当前读线程可并发)");
                Thread.Sleep(500); // 模拟读耗时(如从缓存查询数据)
            }
            finally
            {
                // 释放读锁(必须在 finally 中)
                _rwLock.ExitReadLock();
            }
​
            Thread.Sleep(300); // 模拟两次读之间的间隔
        }
    }
​
    /// <summary>
    /// 写操作(排他锁:仅一个线程可写,写时禁止读)
    /// </summary>
    /// <param name="writerId">写线程ID</param>
    static void WriteCache(int writerId)
    {
        // 循环模拟多次写操作
        for (int i = 0; i < 2; i++)
        {
            // EnterWriteLock:获取写锁(排他锁,仅一个线程可获取)
            // 若已有读锁或写锁,当前线程会阻塞
            _rwLock.EnterWriteLock();
            try
            {
                // 写临界区:修改共享资源(缓存)
                string key = $"key_{writerId}_{i}";
                _cache[key] = $"value_{DateTime.Now:HH:mm:ss.fff}";
                Console.WriteLine($"【写线程 {writerId}】:添加缓存 {key}(当前禁止所有读操作)");
                Thread.Sleep(1000); // 模拟写耗时(如更新缓存数据)
            }
            finally
            {
                // 释放写锁(必须在 finally 中)
                _rwLock.ExitWriteLock();
            }
​
            Thread.Sleep(800); // 模拟两次写之间的间隔
        }
    }
}

4. 同步工具对比与选择建议

同步工具 核心特点 适用场景
lock 简洁、轻量级、基于 Monitor 绝大多数单进程内同步场景(优先选择)
Monitor 灵活(超时、线程通信)、无额外开销 需要精细控制锁的场景
SemaphoreSlim 控制并发数、支持异步 限流、连接池、有限资源访问
Mutex 跨进程同步、重量级 多个程序共享资源(如文件、硬件设备)
ReaderWriterLockSlim 读共享、写排他、高效 读多写少场景(缓存、配置、数据库查询)

四、线程协作:线程通信机制

多线程需协作完成任务时(如生产者 - 消费者问题:生产者生产数据,消费者消费数据),需通过 "线程通信" 实现同步,避免无效等待。

C# 常用线程通信方式:

1. Monitor.Pulse () / Monitor.Wait ()(基于锁)

lock / Monitor 配合使用,适用于单进程内线程通信,轻量级。

核心逻辑:
  • 消费者线程:条件不满足时(无数据),调用 Monitor.Wait() 释放锁,进入等待队列。

  • 生产者线程:生产数据后,调用 Monitor.Pulse() 唤醒一个等待线程,或 PulseAll() 唤醒所有等待线程。

  • 必须在 lock / Monitor 保护的临界区内调用 Wait() / Pulse()

示例:生产者 - 消费者(基于 Monitor)
cs 复制代码
using System;
using System.Collections.Generic;
using System.Threading;
​
class ProducerConsumerMonitorDemo
{
    // 共享队列:生产者放入数据,消费者取出数据
    private static readonly Queue<int> _queue = new Queue<int>();
    
    // 锁对象:用于同步队列操作和线程通信
    private static readonly object _lockObj = new object();
    
    // 程序运行标记:控制生产者和消费者线程的循环
    private static bool _isRunning = true;
​
    static void Main()
    {
        Console.WriteLine("生产者-消费者(基于 Monitor)启动...");
        
        // 启动 1 个生产者线程和 1 个消费者线程
        Thread producerThread = new Thread(Producer)
        {
            Name = "生产者线程",
            IsBackground = true
        };
        Thread consumerThread = new Thread(Consumer)
        {
            Name = "消费者线程",
            IsBackground = true
        };
        
        producerThread.Start();
        consumerThread.Start();
​
        // 让程序运行 5 秒后停止
        Thread.Sleep(5000);
        _isRunning = false; // 设置标记,停止循环
        
        // 等待生产者和消费者线程完成收尾工作
        producerThread.Join();
        consumerThread.Join();
        
        Console.WriteLine("程序结束");
    }
​
    /// <summary>
    /// 生产者线程:生成随机数放入队列
    /// </summary>
    static void Producer()
    {
        Random random = new Random(); // 随机数生成器(用于模拟生产数据)
        
        // 循环生产,直到 _isRunning 为 false
        while (_isRunning)
        {
            // 锁定共享资源(队列)
            lock (_lockObj)
            {
                // 限制队列最大容量为 5(避免队列无限增长)
                if (_queue.Count < 5)
                {
                    int data = random.Next(100); // 生成 0-99 的随机数(模拟生产的数据)
                    _queue.Enqueue(data); // 放入队列
                    Console.WriteLine($"【{Thread.CurrentThread.Name}】:放入数据 {data},队列长度 {_queue.Count}");
                    
                    // Monitor.Pulse():唤醒一个等待在 _lockObj 上的线程(此处唤醒消费者)
                    // 注意:Pulse() 不会立即释放锁,需等当前 lock 块执行完毕
                    Monitor.Pulse(_lockObj);
                }
            }
            
            // 模拟生产间隔(每 500 毫秒生产一个数据)
            Thread.Sleep(500);
        }
        
        Console.WriteLine($"{Thread.CurrentThread.Name}:停止生产");
    }
​
    /// <summary>
    /// 消费者线程:从队列取出数据并消费
    /// </summary>
    static void Consumer()
    {
        // 循环消费,直到 _isRunning 为 false 且队列为空
        while (_isRunning || _queue.Count > 0)
        {
            // 锁定共享资源(队列)
            lock (_lockObj)
            {
                // 队列为空时,消费者等待(释放锁,避免占用 CPU 空转)
                // 用 while 循环判断:避免虚假唤醒(Wait() 可能被无理由唤醒)
                while (_queue.Count == 0 && _isRunning)
                {
                    Console.WriteLine($"【{Thread.CurrentThread.Name}】:队列空,等待生产...");
                    // Monitor.Wait():释放 _lockObj 的锁,当前线程进入等待队列
                    // 直到其他线程调用 Pulse()/PulseAll() 唤醒,且重新获取锁后继续执行
                    Monitor.Wait(_lockObj);
                }
​
                // 队列有数据时,消费数据
                if (_queue.Count > 0)
                {
                    int data = _queue.Dequeue(); // 从队列取出数据
                    Console.WriteLine($"【{Thread.CurrentThread.Name}】:消费数据 {data},队列长度 {_queue.Count}");
                }
            }
            
            // 模拟消费间隔(每 300 毫秒消费一个数据)
            Thread.Sleep(300);
        }
        
        Console.WriteLine($"{Thread.CurrentThread.Name}:停止消费");
    }
}

2. AutoResetEvent / ManualResetEventSlim(事件通知)

基于 "事件信号" 的通信方式,无需依赖锁,适用于线程间简单通知(如 "任务完成" 通知)。

核心区别:
  • AutoResetEvent:调用 Set() 后,自动重置为 "无信号"(仅唤醒一个等待线程)。

  • ManualResetEventSlim:调用 Set() 后,保持 "有信号"(唤醒所有等待线程),需手动调用 Reset() 重置。

示例:任务完成通知(AutoResetEvent)
cs 复制代码
using System;
using System.Threading;

class EventDemo
{
    static void Main()
    {
        Console.WriteLine("事件通知(AutoResetEvent)演示...");
        
        // AutoResetEvent 构造函数:参数为初始信号状态(false=无信号,true=有信号)
        // 初始无信号:工作线程未完成时,主线程等待
        AutoResetEvent doneEvent = new AutoResetEvent(false);

        // 启动工作线程(执行耗时任务)
        Thread workerThread = new Thread(() =>
        {
            Console.WriteLine("工作线程:开始执行任务(模拟 3 秒耗时)");
            Thread.Sleep(3000); // 模拟耗时任务(如下载文件、数据处理)
            Console.WriteLine("工作线程:任务完成");
            
            // Set():发送"完成"信号(将事件状态设为有信号)
            // AutoResetEvent 特性:Set() 后自动重置为无信号(仅唤醒一个等待线程)
            doneEvent.Set();
        })
        {
            Name = "工作线程",
            IsBackground = true
        };
        workerThread.Start();

        Console.WriteLine("主线程:等待工作线程完成...");
        // WaitOne():阻塞主线程,直到事件收到信号(有信号时返回 true)
        // 也可使用 WaitOne(int millisecondsTimeout) 设置超时
        doneEvent.WaitOne();
        
        Console.WriteLine("主线程:工作线程已完成,继续执行后续逻辑");

        // 释放事件资源(避免内存泄露)
        doneEvent.Dispose();
    }
}

// 补充:ManualResetEventSlim 示例(手动重置信号)
class ManualResetEventSlimDemo
{
    static void Main()
    {
        // ManualResetEventSlim:轻量级手动重置事件(比 ManualResetEvent 性能好)
        // 初始无信号(false)
        using (ManualResetEventSlim resetEvent = new ManualResetEventSlim(false))
        {
            // 启动 3 个工作线程
            for (int i = 0; i < 3; i++)
            {
                int threadId = i;
                new Thread(() =>
                {
                    Console.WriteLine($"线程 {threadId}:等待信号...");
                    resetEvent.Wait(); // 阻塞,直到事件有信号
                    Console.WriteLine($"线程 {threadId}:收到信号,执行后续操作");
                })
                {
                    Name = $"线程{threadId}",
                    IsBackground = true
                }.Start();
            }

            // 主线程延迟 2 秒后发送信号
            Thread.Sleep(2000);
            Console.WriteLine("主线程:发送信号(设置事件为有信号)");
            resetEvent.Set(); // 设置为有信号(所有等待线程都会被唤醒)

            // ManualResetEventSlim 不会自动重置,需手动调用 Reset() 恢复无信号
            // resetEvent.Reset(); // 可选:重置为无信号

            // 等待所有线程完成
            Thread.Sleep(1000);
            Console.WriteLine("程序结束");
        }
    }
}

3. CountdownEvent(倒计时门闩)

等待多个线程完成任务后,主线程再继续执行(如 "等待 10 个下载线程全部完成后,合并文件")。

cs 复制代码
using System;
using System.Threading;

class CountdownEventDemo
{
    static void Main()
    {
        int threadCount = 5; // 工作线程数量
        
        // CountdownEvent 构造函数:参数为"倒计时次数"(需调用 Signal() 的次数)
        CountdownEvent countdown = new CountdownEvent(threadCount);

        Console.WriteLine($"主线程:启动 {threadCount} 个工作线程");
        
        // 启动 5 个工作线程
        for (int i = 0; i < threadCount; i++)
        {
            int threadId = i;
            new Thread(() =>
            {
                Console.WriteLine($"工作线程 {threadId}:开始执行任务");
                // 模拟任务耗时(线程 ID + 1 秒,让不同线程完成时间不同)
                Thread.Sleep(1000 * (threadId + 1));
                Console.WriteLine($"工作线程 {threadId}:执行完成");
                
                // Signal():倒计时减 1(每次调用,CurrentCount 减 1)
                countdown.Signal();
            })
            {
                Name = $"工作线程{threadId}",
                IsBackground = true
            }.Start();
        }

        // Wait():阻塞主线程,直到倒计时为 0(所有工作线程调用 Signal())
        countdown.Wait();
        Console.WriteLine("所有工作线程完成,主线程继续执行后续逻辑(如合并结果、生成报告)");
        
        // 释放资源(CountdownEvent 实现了 IDisposable)
        countdown.Dispose();
    }
}

4. Barrier(循环屏障)

多个线程到达 "屏障点" 后,再同时继续执行(如 "多个线程先读取数据,全部读取完成后再统一处理")。

cs 复制代码
using System;
using System.Threading;

class CountdownEventDemo
{
    static void Main()
    {
        int threadCount = 5; // 工作线程数量
        
        // CountdownEvent 构造函数:参数为"倒计时次数"(需调用 Signal() 的次数)
        CountdownEvent countdown = new CountdownEvent(threadCount);

        Console.WriteLine($"主线程:启动 {threadCount} 个工作线程");
        
        // 启动 5 个工作线程
        for (int i = 0; i < threadCount; i++)
        {
            int threadId = i;
            new Thread(() =>
            {
                Console.WriteLine($"工作线程 {threadId}:开始执行任务");
                // 模拟任务耗时(线程 ID + 1 秒,让不同线程完成时间不同)
                Thread.Sleep(1000 * (threadId + 1));
                Console.WriteLine($"工作线程 {threadId}:执行完成");
                
                // Signal():倒计时减 1(每次调用,CurrentCount 减 1)
                countdown.Signal();
            })
            {
                Name = $"工作线程{threadId}",
                IsBackground = true
            }.Start();
        }

        // Wait():阻塞主线程,直到倒计时为 0(所有工作线程调用 Signal())
        countdown.Wait();
        Console.WriteLine("所有工作线程完成,主线程继续执行后续逻辑(如合并结果、生成报告)");
        
        // 释放资源(CountdownEvent 实现了 IDisposable)
        countdown.Dispose();
    }
}

五、线程池:ThreadPool(复用线程,提升性能)

频繁创建 / 销毁线程会产生大量开销(操作系统分配资源、回收资源),ThreadPool 是 .NET 内置的线程池,通过 "复用线程" 减少开销,适用于短期任务。

1. 核心特性

  • 线程池中的线程默认是后台线程

  • 线程池会自动管理线程数量(核心线程数 + 临时线程数),避免线程过多导致的 CPU 切换开销。

  • 不支持设置线程优先级、名称(如需这些特性,需手动创建 Thread)。

2. 线程池的使用(核心方法)

2.1 提交任务:QueueUserWorkItem
cs 复制代码
using System;
using System.Threading;

class ThreadPoolDemo
{
    static void Main()
    {
        Console.WriteLine("线程池示例启动...");
        
        // 1. 提交无参数任务到线程池
        // QueueUserWorkItem:将任务加入线程池队列,线程池空闲时执行
        // 参数:无参数的回调方法(WaitCallback 委托,签名为 void MethodName(object state))
        ThreadPool.QueueUserWorkItem(DoWork);

        // 2. 提交带参数任务到线程池
        // 第二个参数:传递给回调方法的参数(object 类型,需拆箱)
        ThreadPool.QueueUserWorkItem(DoWorkWithParam, "线程池传递的参数");

        Console.WriteLine("主线程:任务已提交到线程池,等待任务完成...");
        
        // 线程池线程是后台线程,主线程退出后任务会终止,因此需要让主线程等待
        Thread.Sleep(3000);
        
        Console.WriteLine("主线程执行完毕");
    }

    /// <summary>
    /// 线程池无参数任务回调(WaitCallback 委托)
    /// </summary>
    /// <param name="state">线程池传递的参数(无参数时为 null)</param>
    static void DoWork(object state)
    {
     //Thread.CurrentThread.ManagedThreadId:获取线程池线程的 ID(用于区分)
        Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):无参数任务执行");
        Thread.Sleep(1000); // 模拟任务耗时(短期任务,适合线程池)
        Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):无参数任务完成");
    }

    /// <summary>
    /// 线程池带参数任务回调
    /// </summary>
    /// <param name="state">线程池传递的参数(需手动拆箱)</param>
    static void DoWorkWithParam(object state)
    {
        // 拆箱:将 object 类型转为 string
        string param = state as string;
        Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):带参数任务执行,参数:{param}");
        Thread.Sleep(1000);
        Console.WriteLine($"线程池线程(ID:{Thread.CurrentThread.ManagedThreadId}):带参数任务完成");
    }
}
2.2 线程池配置(核心线程数、最大线程数)

可通过 ThreadPool.SetMinThreads / ThreadPool.SetMaxThreads 调整线程池参数(需在提交任务前设置):

cs 复制代码
using System;
using System.Threading;

class ThreadPoolConfigDemo
{
    static void Main()
    {
        int workerThreads, completionPortThreads;
        
        // GetMinThreads:获取当前线程池的最小线程数配置
        // workerThreads:工作线程数(处理 CPU 密集型任务)
        // completionPortThreads:IO 完成端口线程数(处理 IO 密集型任务,如异步 IO 回调)
        ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
        Console.WriteLine($"默认最小工作线程数:{workerThreads}");
        Console.WriteLine($"默认最小 IO 完成端口线程数:{completionPortThreads}");

        // GetMaxThreads:获取当前线程池的最大线程数配置
        ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
        Console.WriteLine($"默认最大工作线程数:{workerThreads}");
        Console.WriteLine($"默认最大 IO 完成端口线程数:{completionPortThreads}");

        // SetMinThreads:设置最小工作线程数(IO 密集型任务可适当增大)
        // 最小线程数:线程池会至少维持该数量的空闲线程,避免任务等待
        bool setSuccess = ThreadPool.SetMinThreads(10, completionPortThreads);
        Console.WriteLine($"设置最小工作线程数为 10:{setSuccess}(true=设置成功)");

        // 提交任务到线程池(验证配置)
        for (int i = 0; i < 15; i++)
        {
            int taskId = i;
            ThreadPool.QueueUserWorkItem((state) =>
            {
                Console.WriteLine($"任务 {state}:线程池线程 ID:{Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(500);
            }, taskId);
        }

        // 等待任务完成
        Thread.Sleep(2000);
    }
}

3. 线程池的适用场景与注意事项

  • 适用场景:短期、轻量级任务(如网络请求回调、数据处理)。

  • 不适用场景:

    • 长时间运行的任务(会占用线程池线程,导致其他任务排队)。

    • 需要设置线程优先级、名称的场景。

  • 注意事项:

    • 线程池任务是后台线程,主线程退出后任务会终止,需确保主线程等待。

    • 避免在任务中执行长时间阻塞操作(如 Thread.Sleep(Timeout.Infinite)),会导致线程池线程耗尽。

六、高级并发:Task 与 async/await(.NET 推荐方案)

Task 是 .NET Framework 4.0+ 引入的任务抽象 ,位于 System.Threading.Tasks 命名空间,是 ThreadThreadPool 的高级封装,支持异步等待、结果返回、任务取消、进度报告等,是 .NET 并发编程的首选方案。

1. Task 与 Thread/ThreadPool 的核心区别

特性 Thread(传统线程) ThreadPool(线程池) Task(任务)
资源开销 高(创建 / 销毁成本高) 中(复用线程) 低(基于线程池,支持异步)
返回结果 不支持(需手动处理) 不支持(需手动处理) 原生支持(Task<TResult>
异步等待 Join()(阻塞) 无原生支持(需手动等待) await/Wait()(灵活)
取消机制 无原生支持(Abort 过时) 无原生支持 原生支持 CancellationToken
异常处理 线程内捕获(否则崩溃) 线程内捕获(否则崩溃) 统一捕获(await/Exception
易用性 低(需手动管理) 中(仅支持简单任务) 高(async/await 语法糖)

2. Task 的创建与启动(3 种核心方式)

方式 1:Task.Run(最常用,默认入线程池)

适用于后台任务、CPU/IO 密集型任务,创建即调度(无需手动 Start())。

cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

class TaskRunDemo
{
    // C# 7.1+ 支持 async Main 方法(作为异步程序入口)
    static async Task Main()
    {
        Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):启动任务...");

        // 1. 创建无返回值 Task(Task)
        // Task.Run(Action):将任务提交到线程池,返回 Task 对象(代表任务本身)
        Task task1 = Task.Run(() =>
        {
            Console.WriteLine($"Task1(线程 ID:{Thread.CurrentThread.ManagedThreadId}):无返回值任务执行");
            // Task.Delay(1000):异步休眠 1 秒(不阻塞线程,优于 Thread.Sleep)
            Task.Delay(1000).Wait(); // 此处用 Wait() 是同步等待(仅示例,实际推荐用 await)
        });

        // 2. 创建有返回值 Task(Task<TResult>)
        // Task.Run(Func<TResult>):任务执行完毕后返回 TResult 类型结果
        Task<int> task2 = Task.Run(() =>
        {
            Console.WriteLine($"Task2(线程 ID:{Thread.CurrentThread.ManagedThreadId}):有返回值任务执行");
            Task.Delay(1000).Wait();
            return 10 + 20; // 任务结果(30)
        });

        // 异步等待任务完成(await 不会阻塞主线程,会释放主线程执行其他逻辑)
        await task1; // 等待 task1 完成(无返回值)
        int result = await task2; // 等待 task2 完成并获取结果

        Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):Task2 结果:{result}");
        Console.WriteLine("所有任务执行完毕");
    }
}
方式 2:new Task () + Start ()(手动控制启动)

适用于需要延迟启动的场景(创建后不立即执行)。

cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

class TaskNewStartDemo
{
    static async Task Main()
    {
        Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):创建任务...");

        // 1. 创建无返回值 Task(未启动)
        Task task3 = new Task(() =>
        {
            Console.WriteLine($"Task3(线程 ID:{Thread.CurrentThread.ManagedThreadId}):手动启动的无返回值任务");
            Task.Delay(1000).Wait();
        });

        // 2. 创建有返回值 Task(Task<TResult>,未启动)
        Task<string> task4 = new Task<string>(() =>
        {
            Console.WriteLine($"Task4(线程 ID:{Thread.CurrentThread.ManagedThreadId}):手动启动的有返回值任务");
            Task.Delay(500).Wait();
            return "Task4 执行完成后的返回值";
        });

        // 延迟 1 秒后启动任务(模拟"按需启动"场景)
        Console.WriteLine("主线程:延迟 1 秒后启动任务...");
        Task.Delay(1000).Wait();
        
        task3.Start(); // 必须调用 Start() 方法,任务才会进入调度队列
        task4.Start();

        // 异步等待任务完成
        await task3;
        string strResult = await task4;

        Console.WriteLine($"主线程:Task4 返回值:{strResult}");
    }
}
方式 3:Task.Factory.StartNew(灵活配置任务选项)

支持通过 TaskCreationOptions 配置任务行为(如长时间运行、独立线程)。

cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

class TaskFactoryDemo
{
    static async Task Main()
    {
        Console.WriteLine($"主线程(ID:{Thread.CurrentThread.ManagedThreadId}):启动配置化任务...");

        // Task.Factory.StartNew():灵活配置任务选项
        // 参数 1:任务执行逻辑;参数 2:任务创建选项(TaskCreationOptions)
        Task longTask = Task.Factory.StartNew(() =>
        {
            Console.WriteLine($"长时间任务(线程 ID:{Thread.CurrentThread.ManagedThreadId}):开始执行(独立线程,不占用线程池)");
            // 模拟长时间运行任务(如监控程序、持续数据采集)
            Task.Delay(5000).Wait();
            Console.WriteLine($"长时间任务:执行完成");
        }, 
        // TaskCreationOptions.LongRunning:告诉任务调度器,这是长时间运行的任务
        // 调度器会创建独立的操作系统线程(而非使用线程池线程),避免占用线程池资源
        TaskCreationOptions.LongRunning);

        // 等待长时间任务完成
        await longTask;
        Console.WriteLine("主线程:长时间任务执行完毕");
    }
}

3. Task 的核心高级特性

3.1 异步等待:async/await 语法(.NET 核心)

async/await 是 .NET 异步编程的语法糖,可让异步代码像同步代码一样简洁,避免回调地狱,且不阻塞当前线程(如 UI 线程)。

核心规则:
  • 方法标记为 async,返回值只能是 void(仅用于事件处理)、Task(无返回值)、Task<TResult>(有返回值)。

  • await 只能在 async 方法中使用,用于等待 Task 完成,await 后的代码会在任务完成后执行。

  • UI 程序中,await 后的代码会自动回到 UI 线程(无需手动切换)。

示例:异步下载文件(IO 密集型任务)
cs 复制代码
using System;
using System.Net;
using System.Threading.Tasks;

class AsyncAwaitDemo
{
    /// <summary>
    /// 异步下载文件(IO 密集型任务,推荐用 async/await)
    /// </summary>
    /// <param name="url">文件下载地址</param>
    /// <param name="savePath">本地保存路径</param>
    /// <returns>无返回值 Task(异步方法标记为 async Task)</returns>
    static async Task DownloadFileAsync(string url, string savePath)
    {
        Console.WriteLine($"下载线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):开始下载文件...");
        
        // using 语句:自动释放 WebClient 资源(实现 IDisposable 接口)
        using (WebClient client = new WebClient())
        {
            // DownloadFileTaskAsync:WebClient 的异步下载方法(返回 Task)
            // await:等待异步任务完成,期间不阻塞当前线程(如 UI 线程可继续响应操作)
            // 注意:await 只能在 async 标记的方法中使用
            await client.DownloadFileTaskAsync(new Uri(url), savePath);
        }
        
        Console.WriteLine($"下载线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):文件下载完成");
    }

    /// <summary>
    /// 异步计算(CPU 密集型任务)
    /// </summary>
    /// <param name="a">第一个参数</param>
    /// <param name="b">第二个参数</param>
    /// <returns>有返回值 Task<int>(异步方法标记为 async Task<TResult>)</returns>
    static async Task<int> CalculateAsync(int a, int b)
    {
        Console.WriteLine($"计算线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):开始异步计算...");
        
        // 用 Task.Run 包装 CPU 密集型任务(避免阻塞调用线程)
        // await 等待计算完成,返回结果
        int result = await Task.Run(() =>
        {
            Task.Delay(1000).Wait(); // 模拟计算耗时
            return a + b; // 计算结果
        });
        
        Console.WriteLine($"计算线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):计算完成");
        return result;
    }

    // 异步 Main 方法(程序入口)
    static async Task Main()
    {
        Console.WriteLine($"主线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):启动异步任务...");

        // 1. 调用无返回值异步方法(下载文件)
        // 注意:await 会等待方法执行完成,期间主线程不阻塞(可执行其他逻辑)
        await DownloadFileAsync("https://example.com/test.txt", "test.txt");

        // 2. 调用有返回值异步方法(计算)
        int sum = await CalculateAsync(15, 25);
        Console.WriteLine($"主线程:计算结果:{sum}(预期 40)");

        Console.WriteLine($"主线程(ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}):所有异步任务完成");
    }
}
3.2 任务取消:CancellationToken

通过 CancellationTokenSource(CTS)和 CancellationToken(CT)实现任务的 "优雅取消"(非强制终止),避免资源泄露。

cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

class TaskCancellationDemo
{
    static async Task Main()
    {
        Console.WriteLine("任务取消示例(3 秒后自动取消)...");
        
        // 1. 创建取消令牌源(CancellationTokenSource)
        // 构造函数参数:超时时间(3000 毫秒=3 秒),超时后自动触发取消
        using (CancellationTokenSource cts = new CancellationTokenSource(3000))
        {
            // 获取取消令牌(CancellationToken):传递给任务,用于接收取消信号
            CancellationToken ct = cts.Token;

            // 2. 启动可取消任务
            Task task = Task.Run(() =>
            {
                // 循环执行任务(模拟持续运行的任务)
                for (int i = 0; i < 10; i++)
                {
                    // 检查是否收到取消请求:若已取消,抛出 OperationCanceledException
                    // 必须在任务中主动检查,否则任务不会响应取消
                    ct.ThrowIfCancellationRequested();

                    Console.WriteLine($"任务执行中:第 {i+1} 次循环");
                    Task.Delay(500).Wait(); // 每次循环耗时 500 毫秒
                }
            }, ct); // 将取消令牌传入 Task(用于注册取消回调)

            // 可选:手动取消(如用户点击取消按钮,此处注释掉,用自动超时取消)
            // Task.Run(() =>
            // {
            //     Console.WriteLine("按任意键手动取消任务...");
            //     Console.ReadKey();
            //     cts.Cancel(); // 手动触发取消
            // });

            // 3. 捕获取消异常(OperationCanceledException)
            try
            {
                await task; // 等待任务完成
                Console.WriteLine("任务正常完成(未被取消)");
            }
            catch (OperationCanceledException)
            {
                // 捕获取消异常(正常流程,非错误)
                Console.WriteLine("任务已被成功取消(超时或手动取消)");
            }
        }

        // 注意:using 语句会自动释放 CancellationTokenSource 资源
    }
}
3.3 进度报告:IProgress<T>

用于任务执行过程中向调用方(如 UI 线程)报告进度,无需手动处理线程切换。

cs 复制代码
using System;
using System.Threading.Tasks;

class TaskProgressDemo
{
    static async Task Main()
    {
        Console.WriteLine("带进度报告的任务示例...");

        // 1. 创建进度报告器(IProgress<T>)
        // T 是进度数据类型(此处为 int,代表百分比;也可自定义类)
        // 构造函数参数:进度回调函数(任务报告进度时执行)
        IProgress<int> progress = new Progress<int>(percent =>
        {
            // 关键特性:回调函数会自动在"创建 Progress 对象的线程"(此处是主线程)执行
            // 无需手动切换线程(如 UI 线程无需 Invoke)
            Console.WriteLine($"主线程:当前任务进度:{percent}%");
        });

        // 2. 启动带进度报告的任务
        await DoTaskWithProgress(progress);
        
        Console.WriteLine("任务执行完成");
    }

    /// <summary>
    /// 带进度报告的异步任务
    /// </summary>
    /// <param name="progress">进度报告器(由调用方传入)</param>
    /// <returns>Task(异步任务)</returns>
    static async Task DoTaskWithProgress(IProgress<int> progress)
    {
        // 模拟任务分 11 步执行(0% ~ 100%)
        for (int i = 0; i <= 100; i += 10)
        {
            await Task.Delay(300); // 模拟每步耗时 300 毫秒(如下载、处理数据)
            
            // 报告进度:调用 Report() 方法,传入当前进度
            // 进度报告器会自动将进度回调到创建线程
            progress.Report(i);
        }
    }
}
3.4 批量任务处理:WhenAll / WhenAny
  • Task.WhenAll:等待所有任务完成,返回所有任务的结果(适用于 "所有任务都完成后再继续")。

  • Task.WhenAny:等待任意一个任务完成,返回最先完成的任务(适用于 "取最快完成的任务结果")。

cs 复制代码
using System;
using System.Threading.Tasks;

class TaskBatchDemo
{
    static async Task Main()
    {
        Console.WriteLine("批量任务处理示例(WhenAll + WhenAny)...");

        // -------------------------- 示例 1:Task.WhenAll(等待所有任务)--------------------------
        Console.WriteLine("\n=== 启动 WhenAll 任务(3 个任务并行执行,等待全部完成)===");
        // 创建 3 个有返回值的任务(模拟不同耗时)
        Task<int> taskA = Task.Run(() => { Task.Delay(1000).Wait(); return 1; }); // 1 秒
        Task<int> taskB = Task.Run(() => { Task.Delay(2000).Wait(); return 2; }); // 2 秒
        Task<int> taskC = Task.Run(() => { Task.Delay(1500).Wait(); return 3; }); // 1.5 秒

        // WhenAll:等待所有任务完成,返回所有结果的数组(顺序与传入任务一致)
        int[] allResults = await Task.WhenAll(taskA, taskB, taskC);
        Console.WriteLine($"WhenAll 所有任务完成,结果:{string.Join(", ", allResults)}"); // 输出 1, 2, 3

        // -------------------------- 示例 2:Task.WhenAny(等待任意一个任务)--------------------------
        Console.WriteLine("\n=== 启动 WhenAny 任务(2 个任务并行,取最快完成)===");
        Task<string> fastTask = Task.Run(() => { Task.Delay(500).Wait(); return "快速任务(500 毫秒)"; });
        Task<string> slowTask = Task.Run(() => { Task.Delay(1500).Wait(); return "慢速任务(1500 毫秒)"; });

        // WhenAny:等待任意一个任务完成,返回最先完成的任务对象
        Task<string> completedTask = await Task.WhenAny(fastTask, slowTask);
        
        // 等待完成的任务,获取其结果
        string result = await completedTask;
        Console.WriteLine($"WhenAny 先完成的任务:{result}");

        // 注意:未完成的任务会继续执行,若需取消,可结合 CancellationToken
        // 此处简单等待未完成任务(避免程序提前退出)
        await Task.WhenAll(fastTask, slowTask);
        Console.WriteLine("所有任务均已完成");
    }
}
3.5 任务链:ContinueWith

任务完成后自动执行后续操作(替代回调嵌套),但推荐优先使用 async/await(更简洁)。

cs 复制代码
using System;
using System.Threading.Tasks;

class TaskContinueWithDemo
{
    static async Task Main()
    {
        Console.WriteLine("任务链(ContinueWith)示例...");

        // 任务链:任务 1 完成后执行任务 2,任务 2 完成后执行任务 3
        Task.Run(() =>
        {
            Console.WriteLine("任务 1:执行(模拟 1 秒耗时)");
            Task.Delay(1000).Wait();
        })
        // ContinueWith:当前任务完成后,执行下一个任务(回调函数)
        .ContinueWith((previousTask) =>
        {
            // previousTask:上一个任务的实例(可获取上一个任务的状态、异常等)
            Console.WriteLine($"任务 2:任务 1 已完成(状态:{previousTask.Status}),执行任务 2(模拟 500 毫秒耗时)");
            Task.Delay(500).Wait();
        })
        .ContinueWith((previousTask) =>
        {
            Console.WriteLine($"任务 3:任务 2 已完成(状态:{previousTask.Status}),执行任务 3");
        });

        // 等待任务链完成(避免主线程提前退出)
        await Task.Delay(2000);
        Console.WriteLine("任务链执行完毕");
    }
}

4. Task 的异常处理

Task 的异常不会直接崩溃程序,需通过以下方式捕获(推荐 await + try-catch):

方式 1:await + try-catch(最推荐)
cs 复制代码
using System;
using System.Threading.Tasks;

class TaskExceptionDemo1
{
    static async Task Main()
    {
        Console.WriteLine("Task 异常处理(await + try-catch)示例...");

        try
        {
            // 启动一个会抛出异常的 Task
            await Task.Run(() =>
            {
                Console.WriteLine("任务执行中,准备抛出异常...");
                // 模拟业务异常(参数错误)
                throw new ArgumentException("任务执行时发生参数错误:传入的 ID 无效");
            });
        }
        catch (ArgumentException ex)
        {
            // 直接捕获原始异常(无需处理 AggregateException)
            Console.WriteLine($"捕获到异常:{ex.Message}");
            Console.WriteLine($"异常类型:{ex.GetType().Name}");
        }

        Console.WriteLine("程序继续执行(未因异常崩溃)");
    }
}
方式 2:Task.Exception(非 await 场景)

await 场景下,Task 异常会被包装为 AggregateException,需通过 Flatten() 提取原始异常:

cs 复制代码
using System;
using System.Threading.Tasks;

class TaskExceptionDemo2
{
    static void Main()
    {
        Console.WriteLine("Task 异常处理(Task.Exception)示例...");

        // 启动一个会抛出异常的 Task(未使用 await)
        Task faultTask = Task.Run(() =>
        {
            Console.WriteLine("任务执行中,准备抛出异常...");
            throw new NullReferenceException("任务执行时发生空引用异常:未初始化对象");
        });

        try
        {
            // Wait():阻塞等待任务完成,触发异常
            faultTask.Wait();
            // 若任务有返回值,用 Result 属性也会触发异常:int result = faultTask.Result;
        }
        catch (AggregateException ex)
        {
            // 非 await 场景下,Task 异常会被包装为 AggregateException
            Console.WriteLine($"捕获到 AggregateException:{ex.Message}");
            
            // Flatten():处理嵌套的 AggregateException(如多个任务异常)
            // InnerException:获取原始异常(单个任务时)
            Exception innerEx = ex.Flatten().InnerException;
            Console.WriteLine($"原始异常类型:{innerEx.GetType().Name}");
            Console.WriteLine($"原始异常信息:{innerEx.Message}");
        }

        Console.WriteLine("程序继续执行");
    }
}

5. ValueTask<TResult>(.NET Core 2.1+)

ValueTask<TResult> 是值类型(struct),默认不分配堆内存,适合 "异步操作大概率快速完成" 的场景(如缓存命中、本地文件快速读取),性能优于 Task<TResult>

cs 复制代码
using System;
using System.Threading.Tasks;

class ValueTaskDemo
{
    // .NET Core 2.1+ 支持 ValueTask<TResult>
    static async ValueTask<int> FastOrSlowTask(bool isFast)
    {
        // 场景 1:快速完成(如缓存命中、本地数据读取)
        if (isFast)
        {
            Console.WriteLine("任务快速完成(无堆内存分配)");
            return 100; // 直接返回结果,无需分配 Task 对象(值类型优势)
        }

        // 场景 2:耗时操作(如网络请求、数据库查询)
        // 自动装箱为 Task<int>(仅耗时场景才分配堆内存)
        Console.WriteLine("任务耗时执行(分配堆内存)");
        return await Task.Run(() => 200);
    }

    static async Task Main()
    {
        Console.WriteLine("ValueTask<TResult> 示例...");

        // 测试快速完成场景
        int result1 = await FastOrSlowTask(true);
        Console.WriteLine($"快速任务结果:{result1}(预期 100)");

        // 测试耗时场景
        int result2 = await FastOrSlowTask(false);
        Console.WriteLine($"耗时任务结果:{result2}(预期 200)");
    }
}

七、线程安全集合(避免手动同步)

System.Collections.Concurrent 命名空间提供了线程安全的集合类,内部已实现同步机制,无需手动加锁,适合多线程读写场景。

常用线程安全集合

集合类 功能说明 适用场景
ConcurrentDictionary<TKey, TValue> 线程安全的字典(键值对) 多线程读写键值对(如缓存)
ConcurrentQueue<T> 线程安全的队列(FIFO) 生产者 - 消费者场景(如任务队列)
ConcurrentStack<T> 线程安全的栈(LIFO) 多线程栈操作(如调用栈、撤销操作)
ConcurrentBag<T> 线程安全的无序集合 无需排序的多线程数据存储
BlockingCollection<T> 支持阻塞的线程安全集合(基于队列 / 栈) 生产者 - 消费者场景(自动阻塞空队列读取)

示例:BlockingCollection(生产者 - 消费者简化版)

cs 复制代码
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

class BlockingCollectionDemo
{
    // 1. 初始化阻塞队列:
    // - 泛型参数<int>:指定队列存储int类型数据
    // - 构造参数(5):设置队列最大容量为5,超过容量时生产者会自动阻塞
    // - BlockingCollection内部已实现线程安全,无需手动加锁
    private static readonly BlockingCollection<int> _blockingQueue = new BlockingCollection<int>(5);
    
    // 2. 程序运行控制标记:用于通知生产者停止生产
    private static bool _isRunning = true;

    // 异步Main方法:C# 7.1+支持,作为异步程序入口
    static async Task Main()
    {
        Console.WriteLine("=== 生产者-消费者(基于BlockingCollection)启动 ===");
        
        // 3. 启动生产者和消费者任务(用Task.Run简化线程创建,默认使用线程池)
        // 生产者任务:负责生成数据并放入队列
        Task producerTask = Task.Run(Producer);
        // 消费者任务:负责从队列取出数据并消费
        Task consumerTask = Task.Run(Consumer);

        // 4. 让程序运行5秒后自动停止(模拟业务运行时长)
        await Task.Delay(5000); // 异步休眠,不阻塞主线程(优于Thread.Sleep)
        
        // 5. 停止生产流程:
        _isRunning = false; // 通知生产者退出循环
        // CompleteAdding():标记队列"不再接受新数据",消费者遍历到队尾时会退出
        // 若不调用此方法,消费者的GetConsumingEnumerable()会一直阻塞等待新数据
        _blockingQueue.CompleteAdding();

        // 6. 等待生产者和消费者任务完成收尾工作(避免后台任务被强制终止)
        await Task.WhenAll(producerTask, consumerTask);
        
        Console.WriteLine("=== 程序结束 ===");
    }

    /// <summary>
    /// 生产者方法:生成随机数并放入阻塞队列
    /// </summary>
    static void Producer()
    {
        // 初始化随机数生成器(避免多线程共享时产生重复随机数)
        Random random = new Random();
        
        // 循环生产:只要程序运行标记为true,就持续生成数据
        while (_isRunning)
        {
            // 生成0-99的随机数(模拟业务数据,如订单ID、传感器数据)
            int data = random.Next(100);
            
            // 向队列添加数据:
            // - 若队列未满(<5):直接添加,立即返回
            // - 若队列已满(=5):自动阻塞当前线程,直到队列有空闲位置
            // - 无需手动处理锁和等待逻辑,集合内部已实现
            _blockingQueue.Add(data);
            
            // 输出生产日志:显示当前生产的数据和队列长度
            Console.WriteLine($"【生产者】:添加数据 {data},当前队列长度:{_blockingQueue.Count}");
            
            // 模拟生产间隔:每500毫秒生产一个数据(避免生产过快导致队列一直满)
            Task.Delay(500).Wait(); // 同步休眠(生产者是后台任务,阻塞不影响主线程)
        }
        
        Console.WriteLine("【生产者】:停止生产");
    }

    /// <summary>
    /// 消费者方法:从阻塞队列取出数据并消费
    /// </summary>
    static void Consumer()
    {
        // GetConsumingEnumerable():获取队列的消费枚举器,核心特性:
        // 1. 线程安全:多消费者同时调用也不会出现数据竞争
        // 2. 自动阻塞:队列空时,当前线程会阻塞,直到有新数据或CompleteAdding()被调用
        // 3. 自动退出:当队列空且已调用CompleteAdding(),枚举器会终止,循环退出
        foreach (int data in _blockingQueue.GetConsumingEnumerable())
        {
            // 模拟消费逻辑:如数据处理、存储到数据库等
            Console.WriteLine($"【消费者】:消费数据 {data},当前队列长度:{_blockingQueue.Count}");
            
            // 模拟消费间隔:每300毫秒消费一个数据(消费速度比生产快,避免队列堆积)
            Task.Delay(300).Wait();
        }
        
        Console.WriteLine("【消费者】:停止消费(队列已无数据且停止添加)");
    }
}

八、高级主题:死锁、调优与最佳实践

1. 死锁的成因与避免

死锁是多线程同步时的致命问题:多个线程互相等待对方持有的资源,导致所有线程永久阻塞,程序无法继续执行。

1.1 死锁的 4 个必要条件(缺一不可)
  • 互斥条件 :资源只能被一个线程占用(如lock锁定的对象,同一时间仅一个线程能持有)。

  • 持有并等待条件:线程持有一个资源后,不释放,同时等待另一个资源。

  • 不可剥夺条件 :资源不能被强制从持有线程手中夺走(如lock的锁不能被其他线程强制释放)。

  • 循环等待条件:多个线程形成闭环等待(如线程 A 等线程 B 的资源,线程 B 等线程 A 的资源)。

1.2 死锁示例(循环等待)
cs 复制代码
using System;
using System.Threading;

class DeadlockDemo
{
    // 定义两个独立的锁对象(模拟两个不同的资源,如文件A、数据库连接B)
    private static readonly object _lock1 = new object(); // 资源1的锁
    private static readonly object _lock2 = new object(); // 资源2的锁

    static void Main()
    {
        Console.WriteLine("=== 死锁示例启动(观察控制台输出,程序会永久阻塞)===");
        
        // 创建两个线程,分别执行不同的任务(需要获取两个锁)
        Thread thread1 = new Thread(DoWork1); // 线程1:先拿锁1,再拿锁2
        Thread thread2 = new Thread(DoWork2); // 线程2:先拿锁2,再拿锁1
        thread1.Start(); // 启动线程1
        thread2.Start(); // 启动线程2

        // 等待两个线程完成(实际会永久阻塞,因为死锁)
        thread1.Join();
        thread2.Join();

        Console.WriteLine("程序结束(永远不会执行到这里)");
    }

    /// <summary>
    /// 线程1的任务:先获取_lock1,再获取_lock2
    /// </summary>
    static void DoWork1()
    {
        // 1. 线程1获取资源1的锁(满足"互斥条件")
        lock (_lock1)
        {
            Console.WriteLine("线程1:已持有 资源1(_lock1),正在等待 资源2(_lock2)");
            
            // 模拟业务处理耗时:让线程1有足够时间持有_lock1,让线程2先获取_lock2
            // 此处休眠是为了触发死锁(实际开发中,耗时操作可能是IO、计算等)
            Task.Delay(100).Wait();

            // 2. 线程1尝试获取资源2的锁,但此时资源2已被线程2持有(满足"持有并等待")
            lock (_lock2)
            {
                // 以下代码永远不会执行(死锁已发生)
                Console.WriteLine("线程1:已持有 资源1 和 资源2,执行任务...");
            }
        }
    }

    /// <summary>
    /// 线程2的任务:先获取_lock2,再获取_lock1(与线程1的锁顺序相反)
    /// </summary>
    static void DoWork2()
    {
        // 1. 线程2获取资源2的锁(满足"互斥条件")
        lock (_lock2)
        {
            Console.WriteLine("线程2:已持有 资源2(_lock2),正在等待 资源1(_lock1)");
            
            // 模拟业务处理耗时:让线程2有足够时间持有_lock2,让线程1先获取_lock1
            Task.Delay(100).Wait();

            // 2. 线程2尝试获取资源1的锁,但此时资源1已被线程1持有(满足"持有并等待")
            lock (_lock1)
            {
                // 以下代码永远不会执行(死锁已发生)
                Console.WriteLine("线程2:已持有 资源2 和 资源1,执行任务...");
            }
        }
    }
}
1.3 死锁避免的核心方案(针对 4 个必要条件)
解决方案 作用(破坏哪个条件) 实现方式
统一锁获取顺序 破坏 "循环等待"(最常用) 所有线程按固定顺序获取锁(如先拿_lock1,再拿_lock2,线程 2 修改为与线程 1 一致)
超时获取锁 破坏 "持有并等待" 使用Monitor.TryEnter(lockObj, 100)SemaphoreSlim.Wait(100),超时则释放已持锁
一次性获取所有资源 破坏 "持有并等待" 设计接口,一次性申请所有需要的资源,申请失败则不持有任何资源
允许资源剥夺 破坏 "不可剥夺" 使用CancellationToken,超时或取消时释放已持有的锁
1.4 修复上述死锁示例(统一锁顺序)
cs 复制代码
static void DoWork2()
{
    // 修复:统一锁顺序,先拿_lock1,再拿_lock2(与线程1一致)
    lock (_lock1) 
    {
        Console.WriteLine("线程2:已持有 资源1(_lock1),正在等待 资源2(_lock2)");
        Task.Delay(100).Wait();

        lock (_lock2)
        {
            Console.WriteLine("线程2:已持有 资源1 和 资源2,执行任务...");
        }
    }
}

修复后,线程 1 和线程 2 按相同顺序获取锁,不会形成循环等待,死锁问题解决。

2. 并发编程调优技巧

2.1 线程数调优
  • CPU 密集型任务 (如计算、排序):线程数 ≈ CPU 核心数(Environment.ProcessorCount),避免过多线程导致 CPU 切换开销。

  • IO 密集型任务(如网络请求、数据库操作):线程数可大于 CPU 核心数(如核心数 ×2~5),因为线程大部分时间在等待 IO 完成。

    cs 复制代码
    // 获取CPU核心数
    int cpuCount = Environment.ProcessorCount;
    // IO密集型任务:设置线程池最小工作线程数为CPU核心数×3
    ThreadPool.SetMinThreads(cpuCount * 3, ThreadPool.GetMinThreads(out _, out int ioThreads));
2.2 减少锁竞争
  • 缩小临界区:只在操作共享资源的代码上加锁,避免锁包裹无关耗时操作。

  • 锁粒度拆分:将大锁拆分为多个小锁(如ConcurrentDictionary的分段锁)。

  • 用无锁结构:优先使用System.Collections.Concurrent集合、Interlocked原子操作(如Interlocked.Increment(ref count))。

2.3 避免阻塞线程池
  • 线程池线程是宝贵资源,避免在任务中执行长时间阻塞操作(如Thread.Sleep(Timeout.Infinite)、未设置超时的WaitOne())。

  • 长时间任务:使用TaskCreationOptions.LongRunning标记,让任务调度器创建独立线程(而非线程池线程)。

    cs 复制代码
    // 长时间运行的任务(如监控程序)
    Task longTask = Task.Factory.StartNew(() => 
    {
        while (true) { /* 持续运行 */ }
    }, TaskCreationOptions.LongRunning);

3. 并发编程最佳实践

  1. 优先使用Task+async/await :相比ThreadThreadPool,更简洁、支持取消 / 进度 / 异常处理,是.NET 推荐的异步并发方案。

  2. 避免使用Thread.Abort() :强制终止线程会导致资源泄露(如锁未释放、文件句柄未关闭),用CancellationToken实现优雅取消。

  3. 线程安全集合替代手动同步 :多线程读写集合时,优先使用ConcurrentDictionaryBlockingCollection等,避免手动加锁出错。

  4. 锁对象使用私有只读引用类型 :避免使用thisstring(字符串池复用)作为锁对象,防止外部线程误操作锁。

  5. 异步方法命名规范 :异步方法后缀加Async(如DownloadFileAsync),无返回值时返回Task(而非void,除事件处理程序)。

  6. 捕获并处理任务异常 :避免任务异常未捕获导致程序崩溃,用await + try-catchtask.ContinueWith处理异常。

九、总结

本文覆盖了 C# 线程与并发编程的所有核心知识点 ,从基础的 Thread 类、同步机制、线程通信,到高级的 Taskasync/await、线程池、线程安全集合,再到死锁避免和调优实践,形成了完整的知识体系。

核心学习路径

  1. 理解线程与进程的关系、线程生命周期(基础)。

  2. 掌握 Thread 类的基本使用(创建、启动、等待)。

  3. 学会同步机制(lockSemaphoreSlim 等),解决资源竞争。

  4. 掌握 Task + async/await(重点),应对绝大多数并发场景。

  5. 了解线程池、线程安全集合,优化程序性能。

  6. 规避死锁、合理调优,写出高效稳定的并发程序。

C# 线程与并发核心 API 速查表

(按场景分类,方便开发快速查阅,搭配之前的详细教程使用)

一、基础线程(Thread 类)

类别 API 语法 / 方法 用途说明
构造函数 new Thread(ThreadStart) 创建无参数线程(ThreadStart 委托:void Method()
new Thread(ParameterizedThreadStart) 创建带参数线程(委托:void Method(object)
核心方法 Start() 启动线程(进入就绪状态,仅调用一次)
Join() / Join(int ms) 阻塞当前线程,等待目标线程完成(支持超时)
Sleep(int ms) 线程休眠(释放 CPU,不释放锁,单位:毫秒)
Yield() 主动放弃 CPU 时间片,让同优先级线程执行
属性 IsBackground 设置是否为后台线程(默认 false,前台线程)
Name 线程名称(仅调试用)
Priority 线程优先级(Lowest ~ Highest,默认 Normal)
IsAlive 判断线程是否处于运行 / 阻塞状态
委托 ThreadStart 无参数线程委托(public delegate void ThreadStart()
ParameterizedThreadStart 带参数线程委托(public delegate void ParameterizedThreadStart(object)

二、同步工具(解决资源竞争)

同步工具 核心 API 用途说明
lock 关键字 lock(lockObj) { 临界区 } 简洁同步(底层基于 Monitor,单进程内首选)
Monitor 类 Monitor.Enter(lockObj) 获取锁(需配合 Exit)
Monitor.TryEnter(lockObj, int ms) 尝试获取锁(超时返回 bool,避免死锁)
Monitor.Exit(lockObj) 释放锁(必须在 finally 中调用)
Monitor.Wait(lockObj) 释放锁并等待(线程通信)
Monitor.Pulse(lockObj) 唤醒一个等待线程(线程通信)
SemaphoreSlim new SemaphoreSlim(int initial, int max) 信号量(控制并发线程数,initial:初始可用数,max:最大可用数)
Wait() / Wait(int ms) 获取信号量(无可用则阻塞)
Release() 释放信号量(可用数 +1)
Mutex new Mutex(bool initOwn, string name) 互斥锁(支持跨进程,name:全局唯一名称)
WaitOne(int ms) 等待获取锁(超时返回 bool)
ReleaseMutex() 释放锁
ReaderWriterLockSlim EnterReadLock() 获取读锁(共享,多线程可同时读)
ExitReadLock() 释放读锁
EnterWriteLock() 获取写锁(排他,仅单线程可写)
ExitWriteLock() 释放写锁

三、线程池(ThreadPool)

核心 API 用途说明
ThreadPool.QueueUserWorkItem(WaitCallback) 提交无参数任务到线程池(WaitCallback 委托:void Method(object)
ThreadPool.QueueUserWorkItem(WaitCallback, object) 提交带参数任务到线程池
ThreadPool.GetMinThreads(out int worker, out int io) 获取最小线程数(worker:工作线程,io:IO 完成端口线程)
ThreadPool.SetMinThreads(int worker, int io) 设置最小线程数(IO 密集型任务可增大)
ThreadPool.GetMaxThreads(out int worker, out int io) 获取最大线程数

四、Task 与异步编程(推荐方案)

1. Task 创建与启动

API 语法 用途说明
Task.Run(Action) 启动无返回值任务(入线程池,创建即调度)
Task.Run(Func<TResult>) 启动有返回值任务(返回 Task<TResult>
new Task(Action) / Start() 手动创建任务(需调用 Start () 启动)
new Task<TResult>(Func<TResult>) 手动创建有返回值任务
Task.Factory.StartNew(Action, TaskCreationOptions) 配置化创建任务(如 LongRunning

2. 异步等待(async/await)

语法 用途说明
async Task Method() 无返回值异步方法(不可省略 Task,除非是事件处理)
async Task<TResult> Method() 有返回值异步方法
await Task 异步等待任务完成(不阻塞当前线程)
await Task.Delay(int ms) 异步休眠(优于 Thread.Sleep,不阻塞线程)

3. 任务取消(CancellationToken)

API 语法 用途说明
new CancellationTokenSource(int ms) 创建取消令牌源(支持超时自动取消)
cts.Token 获取取消令牌(传递给任务)
cts.Cancel() 手动触发取消
token.ThrowIfCancellationRequested() 任务内检查取消信号(触发 OperationCanceledException

4. 进度报告(IProgress<T>)

API 语法 用途说明
new Progress<T>(Action<T> callback) 创建进度报告器(回调自动在创建线程执行,无需线程切换)
progress.Report(T value) 任务内报告进度(T 可是 int / 自定义类)

5. 批量任务处理

API 语法 用途说明
Task.WhenAll(params Task[] tasks) 等待所有任务完成(返回 Task/Task<TResult[]>
Task.WhenAny(params Task[] tasks) 等待任意一个任务完成(返回最先完成的 Task
task.ContinueWith(TaskContinuationOptions) 任务完成后执行后续操作(任务链)

6. 异常处理

场景 核心语法
await 场景 try { await task; } catch (Exception) { }
非 await 场景 task.Wait(); → 捕获 AggregateExceptionex.Flatten().InnerException

7. ValueTask<TResult>(.NET Core 2.1+)

API 语法 用途说明
async ValueTask<TResult> Method() 轻量级异步返回(值类型,减少堆分配,适合快速完成的异步操作)

五、线程安全集合(System.Collections.Concurrent)

集合类 核心 API 用途说明
ConcurrentDictionary<TKey,TValue> TryAdd(TKey, TValue) 尝试添加键值对(线程安全,失败返回 false)
GetOrAdd(TKey, Func<TKey,TValue>) 获取或添加(不存在则执行委托创建值)
TryGetValue(TKey, out TValue) 尝试获取值(线程安全)
ConcurrentQueue<T> Enqueue(T) 入队(线程安全)
TryDequeue(out T) 出队(失败返回 false,非阻塞)
ConcurrentStack<T> Push(T) / PushRange(T[]) 入栈(线程安全)
TryPop(out T) / TryPopRange(T[]) 出栈(失败返回 false)
BlockingCollection<T> Add(T) 入队(满时阻塞)
GetConsumingEnumerable() 出队(空时阻塞,配合 CompleteAdding() 停止)
CompleteAdding() 标记集合不再添加数据(让消费者退出循环)

六、线程通信工具

通信工具 核心 API 用途说明
AutoResetEvent new AutoResetEvent(bool initialState) 自动重置事件(initialState:初始信号状态,Set() 后自动重置无信号)
WaitOne() / WaitOne(int ms) 等待信号(阻塞)
Set() 发送信号(唤醒一个线程)
ManualResetEventSlim new ManualResetEventSlim(bool initialState) 手动重置事件(轻量级,Set() 后需 Reset() 重置)
Set() / Reset() 发送信号 / 重置信号
CountdownEvent new CountdownEvent(int count) 倒计时门闩(count:需调用 Signal() 的次数)
Signal() 倒计时减 1
Wait() 阻塞直到倒计时为 0
Barrier new Barrier(int participantCount, Action<Barrier>) 循环屏障(participantCount:参与线程数,回调在所有线程到达后执行)
SignalAndWait() 通知屏障并等待其他线程

七、常用辅助类 / 方法

类 / 方法 用途说明
Thread.CurrentThread 获取当前执行的线程实例
Environment.ProcessorCount 获取 CPU 核心数(线程数调优用)
CancellationToken.None 空取消令牌(无需取消的任务使用)
Task.CompletedTask 已完成的空任务(异步方法返回 "成功完成" 时使用)

八、场景 - API 快速映射

开发场景 推荐 API
简单多线程(需控制优先级 / 后台线程) Thread
单进程内共享资源同步 lock 关键字
限流 / 控制并发数 SemaphoreSlim
跨进程同步(如多程序共享文件) Mutex
读多写少场景(如缓存) ReaderWriterLockSlim
生产者 - 消费者(简单版) BlockingCollection<T>
生产者 - 消费者(基于锁通信) Monitor.Pulse() / Monitor.Wait()
等待多个线程完成 CountdownEvent
多个线程分阶段协作 Barrier
短期轻量级任务(无返回值) ThreadPool.QueueUserWorkItem
异步任务(有返回值 / 取消 / 进度) Task + async/await
快速完成的异步操作(性能优化) ValueTask<TResult>
多线程读写字典 / 队列 ConcurrentDictionary / ConcurrentQueue

希望对大家有所帮助。感谢大家的关注和点赞。

相关推荐
t***31651 小时前
QT开发:事件循环与处理机制的概念和流程概括性总结
开发语言·qt
Dfreedom.1 小时前
机器学习模型误差深度解读:从三类来源到偏差-方差权衡
人工智能·深度学习·机器学习·误差·偏差方差权衡
龙泉寺天下行走1 小时前
[PowerShell 入门教程] 第9.5天(间章):PowerShell 常见 Cmdlet 速查手册
开发语言·php
muxin-始终如一1 小时前
Semaphore 使用及原理详解
java·开发语言·python
roman_日积跬步-终至千里2 小时前
【模式识别与机器学习(8)】主要算法与技术(下篇:高级模型与集成方法)之 元学习
学习·算法·机器学习
名扬9112 小时前
webrtc编译问题-ubuntu
开发语言·python
haing20192 小时前
Bezier曲线曲率极值的计算方法
人工智能·算法·机器学习·曲率极值
白云千载尽2 小时前
Python 初学者 / 中级开发者常踩坑的 10 个坑 —— 要用好几年才能彻底搞清楚的
开发语言·python
Evand J2 小时前
【MATLAB雷达滤波代码】二维,单雷达跟踪与滤波。EKF融合雷达的距离、角度+目标IMU数据。附代码下载链接
开发语言·matlab·雷达·ekf·雷达跟踪·角度观测