CodeGym /课程 /C# SELF /使用 Thread 类启动线程

使用 Thread 类启动线程

C# SELF
第 55 级, 课程 1
可用

1. 介绍

如果把进程想象成一家超市,线程(thread)就像不同收银台的收银员,他们同时为顾客服务。所有收银员都在同一个店里,但每个人并行完成自己的工作,这样效率更高。进程内部的线程允许同时执行多项任务,共享资源并在一个应用内协调工作。最主要的优点是:如果 CPU 支持并行,多线程能真正并行运行。

为什么需要线程(实战场景)

为啥我们要启动多个线程?举几个现实中的例子:

  • 你在写带 GUI 的应用,不想在长时间操作时程序“卡死”。
  • 你需要同时下载多个文件。
  • 在游戏里,敌人需要独立思考,不受其他逻辑阻塞。

令人惊讶的是,很多程序仍然因为不熟练用线程而出现“卡顿”。今天我们学会如何避免这些坑。

Thread 类:手工多线程的基础

Thread 类是 .NET 多线程编程里的老古董。尽管后来出现了更现代的工具(Task、async/await),但学会用 Thread 仍然有价值,尤其想从零开始理解线程内情的时候。

创建线程的步骤

  1. 创建一个 Thread 对象,传入将在线程中执行的方法。
  2. 用 Start() 启动线程。
  3. (可选)观察运行情况——毕竟一切可能是并行的!

2. 用 Thread 启动线程

来实操感受并行的魔力。我们给条件应用加个小类,让它数到某个数并输出进度,学习如何把这项工作放到单独线程里运行。

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("主线程启动啦!");

        // 创建一个 Thread 对象,传入要在线程中执行的方法
        Thread workerThread = new Thread(CountToTen);

        // 启动新线程
        workerThread.Start();

        // 主线程也在干点事:输出点点...
        for (int i = 0; i < 5; i++)
        {
            Console.Write(".");
            Thread.Sleep(500); // 为了看得清楚延迟一下
        }

        Console.WriteLine("\n主线程结束啦!");
    }

    static void CountToTen()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[线程] 计数:{i}");
            Thread.Sleep(400);
        }
        Console.WriteLine("[线程] 完成!");
    }
}

发生了什么?

你会在控制台看到点点和 "计数:X" 交错出现。这是多线程的第一个迹象!主线程打印它的点点,而新线程数到 10。它们互不干扰,就像乐队里两个乐手:一个打鼓,一个弹琴,各自发声,合起来就是一首歌。

3. 怎么给线程传数据?

有时候线程不仅要知道做什么,还要知道操作哪个数据。如果线程的方法带参数,怎么把参数传进去?

方法一:用 lambda(匿名方法)

int bounds = 7;
Thread t = new Thread(() => CountToNumber(bounds));
t.Start();
static void CountToNumber(int n)
{
    for (int i = 1; i <= n; i++)
    {
        Console.WriteLine($"[线程] {i} / {n}");
        Thread.Sleep(300);
    }
}

这里我们把想要调用的方法包在 lambda 里,从而传入参数。这很常见,因为 Thread 要的是无参方法(ThreadStart)。

方法二:用 ParameterizedThreadStart

也可以用专门的委托 ParameterizedThreadStart,它接受一个 object 类型的参数。

Thread t = new Thread(CountToNumberObject);
t.Start(12);

static void CountToNumberObject(object? n)
{
    int max = (int)n!;
    for (int i = 1; i <= max; i++)
    {
        Console.WriteLine($"[线程] {i} / {max}");
        Thread.Sleep(200);
    }
}

参数类型是 object,所以需要做类型转换。不是特别优雅,但能用。现代 C# 更推荐用 lambda 的方式。

4. 线程生命周期的管理

来看看 Thread 提供了哪些实用东西。

属性 / 方法 用途
Thread.Start()
启动线程(执行创建时指定的方法)
Thread.Join()
等待线程结束(阻塞调用线程直到另一个线程完成)
Thread.IsAlive
指示线程当前是否在运行(true/false)
Thread.Name
给线程命名(对调试很有帮助)
Thread.CurrentThread
获取表示当前线程的对象
Thread.Sleep(ms)
让当前线程休眠 ms 毫秒

示例:等待线程结束

有时候希望主线程等辅助线程干完再继续。

Thread t = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"[第二线程] {i}");
        Thread.Sleep(300);
    }
});
t.Start();

Console.WriteLine("[主线程] 等待第二个线程结束...");
t.Join(); // 主线程在这里等

Console.WriteLine("[主线程] 第二个线程已结束!");

没有 Join(),程序可能在线程还没结束时就退出了。有了 Join(),主线程会耐心等所有任务完成。

5. 有用的小细节

给线程命名:别弄混了

调试时给线程起个名字会很方便:

Thread t = new Thread(() =>
{
    Console.WriteLine($"这在线程里执行:{Thread.CurrentThread.Name}");
});
t.Name = "计数线程";
t.Start();

当线程变多,每个线程做不同事情时,这会很有帮助。

限制和现实中的趋势

直接用 Thread 手动管理线程在现代应用里越来越少见。实际项目通常用更强大的工具(Task、async/await),我们之后会详细讲。不过理解线程基础很重要,适用于:

  • 理解 C# 和 .NET 的“幕后”运作。
  • 面试时(有时候会被问到 Thread 和 Task 的区别)。
  • 排查和修复大型“遗留”应用里的复杂问题。

总结图:线程的生命周期

stateDiagram-v2
    [*] --> New: Thread 已创建
    New --> Running: Start()
    Running --> Stopped: 方法结束
    Stopped --> [*]

现在你已经会自己创建并启动 C# 线程了。你不再只是火车上的乘客,而是能同时操控好几节车厢的司机!接下来会讲线程生命周期、管理、同步以及并行的新天地。

6. 使用 Thread 时的常见错误和小技巧

错误 №1:忘记启动线程。
常见情况是创建了 Thread 对象,却忘了调用 Start(),结果线程根本没跑,排查起来挺迷惑的。

错误 №2:在没有同步的情况下修改共享数据。
如果多个线程操作同一个变量但没加保护,问题肯定会出现!就像两个收银员从同一个钱箱找零——很快会乱套。

错误 №3:使用过时且不安全的方法。
别用 Thread.Suspend()、Thread.Resume() 这类方法——它们被认为危险且过时。用其他方式管理线程生命周期。

错误 №4:线程内未处理的异常。
如果线程里抛了异常但没捕获,线程会终止,而主线程可能完全不知道这事!把线程代码包到 try-catch 里,记录错误。

Thread t = new Thread(() =>
{
    try
    {
        // ... 你的代码
    }
    catch (Exception ex)
    {
        // 记录错误
        Console.WriteLine($"[线程] 错误:{ex.Message}");
    }
});
t.Start();
2
任务
C# SELF, 第 55 级, 课程 1
已锁定
等待线程结束(Join)
等待线程结束(Join)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION