CodeGym /课程 /C# SELF /线程的生命周期与管理

线程的生命周期与管理

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

1. Введение

想像一下线程是个不知疲倦的员工,你把工作交给他。员工可能会睡觉(还没开始工作)、拼命工作(正在执行你的方法)、等你分配新任务(空闲),或者干完活(结束执行)。

在 C#(以及整个 .NET)里,线程的生命周期有几种状态:

  • Unstarted — 线程已创建,但还没启动。
  • Running — 线程正在执行。
  • WaitSleepJoin — 线程暂时不工作(例如,等待信号或“睡眠”)。
  • Stopped — 线程完成任务并已结束。

可以用下面的图直观表示这个循环:

stateDiagram-v2
    [*] --> Unstarted
    Unstarted --> Running: Start()
    Running --> WaitSleepJoin: Wait/Sleep/Join
    WaitSleepJoin --> Running: 收到信号/时间到
    Running --> Stopped: 方法结束
    WaitSleepJoin --> Stopped: 方法结束
    Stopped --> [*]

一切从创建 Thread 对象开始,但在你调用 Start() 之前,线程在 Unstarted 状态“打盹”。调用 Start() 后——精彩开始,线程进入 Running。如果线程内部调用 Thread.Sleep 或者在等待什么(比如 Monitor.Wait),它会进入特殊的等待状态。一旦传给线程的方法结束,线程就结束,不会再“复活”。就是单程票。

2. Практика: Жизненный цикл простого потока

来看个经典例子:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 创建线程 — 还没开始做事
        Thread worker = new Thread(DoWork);

        Console.WriteLine($"线程创建后的状态: {worker.ThreadState}");

        // 启动线程
        worker.Start();
        Console.WriteLine($"线程启动后的状态: {worker.ThreadState}");

        // 让主线程睡一下,给工作线程一点时间
        Thread.Sleep(100);

        Console.WriteLine($"线程状态(稍后): {worker.ThreadState}");

        // 等待 worker 结束(Join)
        worker.Join();

        Console.WriteLine($"线程结束后的状态: {worker.ThreadState}");
        Console.WriteLine("主线程已结束");
    }

    static void DoWork()
    {
        Console.WriteLine("工作线程开始工作!");
        Thread.Sleep(500);
        Console.WriteLine("工作线程完成工作!");
    }
}

程序会输出什么?

  1. 创建后,状态会是 Unstarted
  2. 启动后——通常是 Running(但也可能是 Running | Background)。
  3. 运行期间——状态可能是 Running,或是 WaitSleepJoin(如果线程在“睡眠”)。
  4. 方法结束后——状态变为 Stopped

这段代码是理解线程可能处于哪些状态的好工具。可以玩玩延迟设置,看看状态如何变化。

3. Управление потоком: основные методы

Запуск: Start()

这很明显,但再说一遍:创建线程后用 Start() 启动。注意只能启动一次:再次调用 Start() 会抛出 ThreadStateException

Thread t = new Thread(MyMethod);
t.Start();   // 可以
t.Start();   // 错误!

Ожидание завершения: Join()

有时候需要等线程完成后再继续,这时用 Join()

Thread t = new Thread(MyMethod);
t.Start();
t.Join(); // 阻塞当前线程直到 t 完成

如果有多个线程,可以对每个都调用 Join() —— 主线程会等所有“工人”完成。

变体:有重载 Join(int millisecondsTimeout),它只等待指定时间然后继续。

// 等待不超过 2 秒
if (t.Join(2000))
    Console.WriteLine("线程及时结束");
else
    Console.WriteLine("等得烦了...");

Принудительная остановка: почему это плохая идея

在老版本 .NET 有个方法 Thread.Abort(),可以把线程随时“杀掉”。现在很少用了——它危险,可能把程序搞成奇怪的状态。.NET 的哲学是:线程应该自愿结束。你不是“杀死”员工,而是礼貌地提醒他下班了。

4. Как корректно "остановить" поток

最正确且安全的停止方式是用一个取消标志或结束标识,线程定期检查它。

class Worker
{
    private volatile bool shouldStop = false;

    public void DoWork()
    {
        while (!shouldStop)
        {
            Console.WriteLine("正在工作!");
            Thread.Sleep(300);
        }

        Console.WriteLine("线程按命令结束工作。");
    }

    public void RequestStop()
    {
        shouldStop = true;
    }
}

示例用法:

Worker w = new Worker();
Thread t = new Thread(w.DoWork);
t.Start();

// 等一会儿
Thread.Sleep(1000);

// 请求线程结束
w.RequestStop();
t.Join(); // 等待线程结束

Важный момент: volatile

关键字 volatile 告诉编译器和 CPU: "不要缓存这个字段,总是读最新值!" 这很重要,能让线程看到最新的停止标志。没有它(或其他同步手段),线程可能永远看不到你的更改。

5. Переход потоков в состояния ожидания и сна

有时候线程暂时不做事——要么在等待,要么在睡眠。

Сон: Thread.Sleep

想让线程休息或减缓执行(比如不想压满 CPU),用 Thread.Sleep(milliseconds)

// 线程睡眠 2 秒
Thread.Sleep(2000);

睡眠期间线程不做任何工作。

Ожидание / Join

当主线程等待子线程结束(Join)时,主线程“暂停”。同样地,如果线程在等某个资源(比如用 monitor 或其他同步原语),它会进入特殊的等待状态。

6. Управление фоновостью потока

在 .NET 中线程分两类:foreground(前台)和 background(后台)。区别很简单:

  • 如果进程里只剩下后台线程,进程会自动结束。
  • 主线程和所有前台线程必须结束,进程才会停止。

可以显式把线程设为后台:

Thread t = new Thread(SomeMethod);
t.IsBackground = true; // 设为后台
t.Start();

Практический пример — Демон vs. Обычный поток

Thread t = new Thread(() =>
{
    while (true)
    {
        Console.WriteLine("我是幽灵(后台),停不下来的!");
        Thread.Sleep(500);
    }
});
t.IsBackground = true; // 设为后台
t.Start();

Thread.Sleep(1200);
Console.WriteLine("主线程结束工作");
// Main 结束后 —— 进程终止,我们那个死循环线程也会被结束

Main 结束后,进程会终止;后台线程会被自动停止。

7. Полезные нюансы

Чего нельзя делать с потоками

  • 不能“重启”线程。 Thread 对象只能活一次:方法一旦结束——线程就死了,再调用 Start() 会报错。
  • 不能用强制手段停止别人的线程(比如 Thread.Abort()Thread.Suspend())— 这些是过时且危险的。
  • 不能忽视线程结束时的资源释放。 如果线程操作文件或其他资源,务必在结束前正确释放它们。

Проверка состояния и управление жизненным циклом

if (t.IsAlive)
{
    Console.WriteLine("线程仍在运行");
}
else
{
    Console.WriteLine("线程已结束");
}

IsAlivetrue,只要线程在执行它的方法;结束后 — false

Жизненный цикл простого потока в .NET

状态 如何进入 这是什么意思? 如何离开
Unstarted
new Thread(...)
线程已创建,未启动 调用 Start()
Running
Start()
线程正在工作 方法结束
WaitSleepJoin Sleep(), Join(), 等待 线程暂时不活动 等待结束
Stopped 线程方法已结束 线程已终止 不会再返回 — 结束

FAQ по управлению жизнью потока

Вопрос: 可以按命令杀死线程吗?
Ответ: 不,也不需要,线程应自己负责结束。使用取消标志。

Вопрос: 可以重新使用 объект Thread?
Ответ: 不行。为新工作创建新的对象。

Вопрос: 如果主线程结束但子线程还在运行会怎样?
Ответ: 如果子线程是后台线程(IsBackground == true),应用会结束。如果不是 — 进程会一直存活直到所有线程结束。

Вопрос: 如果线程因取消而结束,如何正确清理资源?
Ответ: 在线程方法内部使用块 try...finally,以确保资源在任何情况下都被释放。

8. Типичные ошибки и как их избежать при работе с потоками

Ошибка №1: повторное использование одного и того же объекта Thread.
不能把同一个线程对象启动多次。线程结束后不能重启——这会导致异常。

Ошибка №2: неправильное освобождение внешних ресурсов в потоке.
如果线程处理文件、网络或其他资源,务必正确关闭并释放。建议使用块 finally 或者构造 using,以避免泄漏和死锁。

Ошибка №3: создание слишком большого количества потоков.
创建过多线程会让调试变复杂,且可能降低性能。多一个线程就多一堆调试时间和意外 bug。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION