C# 多线程实战指南:从线程创建到管理与终止

多线程是提升 C# 程序并发能力的核心手段,适用于处理耗时操作(如 IO、网络请求、计算密集型任务)。本文从实战角度讲解多线程的创建、管理和优雅终止,全程使用可直接运行的代码示例,贴合新手学习习惯。

前置准备

所有多线程操作都依赖 System.Threading 命名空间,首先在代码中引入:

csharp 复制代码
using System;
using System.Threading;
// 后续用到Task/CancellationToken时需要
using System.Threading.Tasks;

环境要求:.NET Framework 4.0+ / .NET Core 2.0+ / .NET 5+(所有现代 C# 环境均支持)。


一、线程创建:从基础到推荐方式

C# 中创建线程主要有 3 种方式,优先级:Task(推荐)> Thread(基础)> ThreadPool(底层)。

1. 基础方式:Thread 类(无参数)

使用 ThreadStart 委托创建无参数线程,核心方法是 Start() 启动线程。

csharp 复制代码
// 示例:创建无参数线程
class ThreadCreationDemo
{
    static void Main()
    {
        // 1. 定义线程要执行的方法
        void PrintNumbers()
        {
            for (int i = 1; i <= 5; i++)
            {
                // Thread.CurrentThread.ManagedThreadId:获取当前线程ID
                Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}:数字 {i}");
                // 模拟耗时操作(暂停500毫秒)
                Thread.Sleep(500);
            }
        }

        // 2. 创建线程实例
        Thread thread = new Thread(PrintNumbers);

        // 3. 启动线程(此时线程进入就绪状态,等待CPU调度)
        thread.Start();

        // 主线程继续执行(验证并发)
        Console.WriteLine($"主线程 {Thread.CurrentThread.ManagedThreadId}:我在主线程执行");

        // 等待子线程执行完毕(否则主线程退出会直接结束程序)
        thread.Join();
    }
}

运行结果(线程 ID 可能不同,顺序非固定):

复制代码
主线程 1:我在主线程执行
线程 3:数字 1
线程 3:数字 2
线程 3:数字 3
线程 3:数字 4
线程 3:数字 5

2. Thread 类(带参数)

使用 ParameterizedThreadStart 委托,参数必须是 object 类型(需手动转换)。

csharp 复制代码
static void Main()
{
    // 带参数的线程方法
    void PrintMessage(object msgObj)
    {
        string message = msgObj as string;
        if (message == null) message = "默认消息";
        
        Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}:{message}");
        Thread.Sleep(1000);
    }

    // 创建线程并传入参数
    Thread thread = new Thread(PrintMessage);
    // 启动时传入参数
    thread.Start("Hello 多线程!");

    thread.Join();
}

3. 推荐方式:Task(基于线程池,更高效)

Task 是 .NET 4.0+ 推出的现代多线程方案,底层复用线程池,避免手动创建线程的开销,支持异步编程(async/await)。

csharp 复制代码
static void Main()
{
    // 方式1:创建并启动Task
    Task task1 = Task.Run(() => 
    {
        Console.WriteLine($"Task线程 {Thread.CurrentThread.ManagedThreadId}:执行任务1");
        Thread.Sleep(1000);
    });

    // 方式2:带返回值的Task
    Task<int> task2 = Task.Run(() => 
    {
        Console.WriteLine($"Task线程 {Thread.CurrentThread.ManagedThreadId}:执行计算任务");
        Thread.Sleep(1000);
        return 100 + 200;
    });

    // 等待所有Task完成
    Task.WaitAll(task1, task2);
    // 获取带返回值Task的结果
    Console.WriteLine($"计算结果:{task2.Result}");
}

核心优势 :Task 自动管理线程生命周期,无需手动调用 Join(可通过 Wait/WaitAll 等待),支持取消、异常捕获等高级特性。


二、线程管理:核心技巧

1. 等待线程完成(Join/Wait)

  • Thread.Join():阻塞当前线程,直到目标线程执行完毕(适用于 Thread 类)。
  • Task.Wait():阻塞当前线程,直到 Task 完成(适用于 Task 类)。
csharp 复制代码
static void Main()
{
    Thread t = new Thread(() => 
    {
        Thread.Sleep(2000);
        Console.WriteLine("子线程执行完毕");
    });
    t.Start();

    Console.WriteLine("等待子线程完成...");
    // 主线程阻塞,直到t执行完毕
    t.Join();
    Console.WriteLine("主线程继续执行");
}

2. 前台/后台线程

  • 前台线程:默认类型,只要有一个前台线程运行,进程就不会退出。
  • 后台线程:进程退出时会强制终止,适合执行非关键任务(如日志收集)。
csharp 复制代码
static void Main()
{
    Thread t = new Thread(() => 
    {
        // 无限循环(验证后台线程行为)
        while (true)
        {
            Console.WriteLine("后台线程运行中...");
            Thread.Sleep(1000);
        }
    });
    // 设置为后台线程
    t.IsBackground = true;
    t.Start();

    // 主线程暂停3秒后退出
    Thread.Sleep(3000);
    Console.WriteLine("主线程退出,进程结束");
}

结果:主线程退出后,后台线程被强制终止,不会无限输出。

3. 线程优先级

通过 Thread.Priority 设置优先级(影响 CPU 调度优先级,不保证执行顺序 ),可选值:Lowest < BelowNormal < Normal < AboveNormal < Highest

csharp 复制代码
static void Main()
{
    Thread t1 = new Thread(() => 
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("低优先级线程:" + i);
            Thread.Sleep(100);
        }
    });
    t1.Priority = ThreadPriority.Lowest;

    Thread t2 = new Thread(() => 
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("高优先级线程:" + i);
            Thread.Sleep(100);
        }
    });
    t2.Priority = ThreadPriority.Highest;

    t1.Start();
    t2.Start();

    t1.Join();
    t2.Join();
}

4. 线程本地存储(ThreadLocal)

解决多线程共享变量的问题,每个线程拥有独立的变量副本:

csharp 复制代码
static void Main()
{
    // 线程本地存储:每个线程有自己的count副本
    ThreadLocal<int> count = new ThreadLocal<int>(() => 0);

    // 启动3个线程,各自累加count
    for (int i = 0; i < 3; i++)
    {
        new Thread(() => 
        {
            for (int j = 0; j < 5; j++)
            {
                count.Value++;
                Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}:count = {count.Value}");
            }
            // 释放资源
            count.Dispose();
        }).Start();
    }

    Thread.Sleep(2000);
}

三、线程终止:优雅终止而非强制杀死

1. 绝对避免:Thread.Abort()(已废弃)

Abort() 会强制抛出 ThreadAbortException 终止线程,可能导致:

  • 资源泄漏(如未释放文件句柄、数据库连接);
  • 数据损坏(线程执行到一半被终止);
  • .NET Core/.NET 5+ 中已标记为废弃,不推荐使用。

2. 推荐方式1:布尔标志位(简单场景)

通过共享布尔变量控制线程循环,实现优雅终止:

csharp 复制代码
class GracefulStopDemo
{
    // 线程终止标志(必须用volatile,保证多线程可见性)
    private static volatile bool _isStop = false;

    static void Main()
    {
        Thread workerThread = new Thread(Work);
        workerThread.Start();

        // 主线程等待5秒后,触发终止
        Console.WriteLine("5秒后终止子线程...");
        Thread.Sleep(5000);
        _isStop = true;

        workerThread.Join();
        Console.WriteLine("子线程已优雅终止");
    }

    static void Work()
    {
        int count = 0;
        // 循环判断终止标志
        while (!_isStop)
        {
            Console.WriteLine($"子线程运行中,计数:{count++}");
            Thread.Sleep(1000);
        }
        // 终止前的清理工作(释放资源、保存数据等)
        Console.WriteLine("子线程执行清理工作...");
    }
}

关键volatile 关键字确保多个线程能实时看到变量的最新值,避免缓存导致的判断失效。

3. 推荐方式2:CancellationToken(规范场景)

CancellationToken 是 .NET 官方推荐的取消机制,支持批量取消、注册回调等高级功能,适配 Task/Thread。

csharp 复制代码
static void Main()
{
    // 1. 创建取消令牌源
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken token = cts.Token;

    // 2. 启动线程/Task,传入取消令牌
    Task workerTask = Task.Run(() => 
    {
        int count = 0;
        while (!token.IsCancellationRequested)
        {
            Console.WriteLine($"Task运行中,计数:{count++}");
            Thread.Sleep(1000);
        }
        // 可选:抛出取消异常(符合.NET取消规范)
        token.ThrowIfCancellationRequested();
    }, token);

    // 3. 3秒后触发取消
    Console.WriteLine("3秒后取消Task...");
    Thread.Sleep(3000);
    cts.Cancel(); // 发送取消信号

    try
    {
        workerTask.Wait();
    }
    catch (AggregateException ex)
    {
        // 捕获取消异常(正常流程,无需处理)
        if (ex.InnerException is TaskCanceledException)
        {
            Console.WriteLine("Task已被优雅取消");
        }
        else
        {
            // 处理其他异常
            Console.WriteLine("异常:" + ex.Message);
        }
    }
    finally
    {
        // 释放令牌源资源
        cts.Dispose();
    }
}

优势 :支持注册取消回调(token.Register(() => { 清理逻辑 })),适合复杂场景(如多任务批量取消)。


总结

关键点回顾

  1. 创建线程 :优先使用 Task(高效、易管理),仅在需精细控制线程(如前台/后台、优先级)时使用 Thread 类。
  2. 管理线程 :通过 Join/Wait 等待线程完成,后台线程不会阻塞进程退出,ThreadLocal<T> 解决线程共享变量问题。
  3. 终止线程 :绝对避免 Abort(),简单场景用 volatile 布尔标志位,规范场景用 CancellationToken 实现优雅终止,终止前务必执行资源清理。

最佳实践

  • 耗时 IO 操作(如网络请求、文件读写)优先使用 async/await(基于 Task 的异步编程),而非手动创建线程;
  • 计算密集型任务可使用 Task + 线程池,避免创建大量 Thread 导致的资源耗尽;
  • 所有多线程操作需注意线程安全(如锁 lock、原子操作 Interlocked),避免竞态条件。
相关推荐
我是唐青枫2 小时前
C#.NET 源生成器 深入解析:编译时代码生成与增量生成器实战
c#·.net
人工智能AI技术2 小时前
GTC 2026首日:C#对接NVIDIA物理AI,工业仿真开发全流程
人工智能·c#
人工智能AI技术2 小时前
315 AI乱象下的C#解法:构建可信、可审计的AI应用实战
人工智能·c#
猹叉叉(学习版)3 小时前
【ASP.NET CORE】 12. DDD基本概念
笔记·后端·架构·c#·asp.net·.netcore
Sunsets_Red3 小时前
模意义下及同余的公式整理
c语言·c++·数学·算法·c#·数论·信息学竞赛
唐青枫3 小时前
C#.NET Pipelines 深入解析:高性能 IO 管道与零拷贝协议处理实战
c#·.net
江沉晚呤时4 小时前
C# 接口默认实现与依赖注入实战指南:.NET 9 企业级开发高级技巧
c#·log4j·.net·.netcore
csdn_aspnet12 小时前
如何用 C# 和 Gemma 3 在本地构建一个真正能完成工作的 AI 代理的
人工智能·ai·c#·gemma
我是唐青枫13 小时前
C#.NET Span 深入解析:零拷贝内存切片与高性能实战
开发语言·c#·.net