CodeGym /课程 /C# SELF /线程优先级和线程类型

线程优先级和线程类型

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

1. 介绍

把线程想像成排队买冰淇淋的人。有些人安静地站着,有些人会吵嚷("我快赶不上火车了,请让我先!"),店员似乎能听见所有人,但有时候会让某些人插队——比如抱着哭闹孩子的妈妈。线程优先级大概就是这么回事。

在 C#(确切说是 .NET)里,每个线程都有优先级——这是给操作系统的一个提示,表示这个线程相对于其他线程“更重要”。这不是对操作系统的强制命令(没人保证最高优先级的线程总能拿到全部注意力),但通常高优先级线程会得到更多的 CPU 时间。

这在什么场景真正有用?

  • 在 UI:比如屏幕更新不应该被低优先级的重计算拖慢。
  • 在游戏里:渲染帧比后台的地图生成更重要。
  • 在对响应时间有严格要求的任务:设备控制、信号处理等。

2. 线程优先级:工作原理

在 C# 里处理线程优先级很简单——Thread 对象有一个 Priority 属性。它接受来自枚举 ThreadPriority 的值:

说明
Lowest
最低优先级
BelowNormal
低于中等
Normal
普通(默认)
AboveNormal
高于中等
Highest
最高优先级

代码示例:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread lowPriorityThread = new Thread(PrintLowPriority);
        Thread highPriorityThread = new Thread(PrintHighPriority);

        lowPriorityThread.Priority = ThreadPriority.Lowest;
        highPriorityThread.Priority = ThreadPriority.Highest;

        lowPriorityThread.Start();
        highPriorityThread.Start();
    }

    static void PrintLowPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("低优先级: " + i);
            Thread.Sleep(10); // 加个暂停,好看出差别
        }
    }

    static void PrintHighPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("高优先级: " + i);
            Thread.Sleep(10);
        }
    }
}

提醒

在实际中,特别是在现代多核系统和托管的 .NET 环境里,优先级只是给操作系统的建议。操作系统(和 .NET)会尽量分配更多时间给高优先级线程,但没有铁定保障。比如,如果你的线程用 Highest 占满了 CPU,界面可能会卡住,其他线程会受影响。

3. 修改线程优先级

线程和优先级

graph LR
    A[线程 1 - Lowest] -->|更少 CPU 时间| OS(操作系统)
    B[线程 2 - Normal] --> OS
    C[线程 3 - Highest] -->|更多 CPU 时间| OS
    OS --> CPU(处理器)

为什么要改线程优先级?

你可能会觉得总是把优先级设成 Highest 就行了,但那是个坏主意!想象一下,如果商店里所有顾客同时喊 "先服务我!"

只有在有理由的时候才改优先级:

  • 日志写入的后台线程——可以设为 BelowNormalLowest
  • 处理用户输入的线程——设为 AboveNormal
  • 不需要及时完成的 CPU 密集任务——低优先级。

小技巧: 如果应用变慢了,检查有没有“嚣张”的高优先级线程在抢资源,影响了其他工作!

4. .NET 中的线程类别

在 C# 里通常把线程分成两类:foregroundbackground。有意思的是,"background" 并不是字面上“在后台悄悄运行”的意思。下面解释清楚。

Foreground 线程(前台)

  • 默认情况下你创建的线程都是 foreground。
  • 只要进程里至少有一个 foreground 线程存活,应用就不会退出,即使其他都结束了。
  • 例子:程序的主线程(Main),以及通过 new Thread() 创建且未改动的线程。

Background 线程(后台)

  • 如果所有 foreground 线程都结束了,只剩下 background 线程,进程会直接退出,background 线程会被强制中断,没有任何预警。
  • 用于次要任务:日志、后台发送指标等。
  • 把线程设为后台:把 IsBackground 设为 true
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();

演示差异

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread backgroundThread = new Thread(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Background thread 工作中... " + i);
                Thread.Sleep(500);
            }
            Console.WriteLine("Background 线程 已完成。");
        });

        backgroundThread.IsBackground = true; // 把线程设为后台!

        backgroundThread.Start();

        Console.WriteLine("Main 将在 1 秒后结束。");
        Thread.Sleep(1000); // 等一秒
        Console.WriteLine("Main 已结束。后台线程会怎样?");
    }
}

你会看到什么?
Main 可能结束,后台线程会在半途中被终止。这对那些不应该阻止应用退出的任务很方便。

5. 线程的种类

ThreadPool(线程池)

  • ThreadPool 是一个管理线程池的机制,避免频繁创建销毁线程。
  • 适用于任务多且短的场景:并行处理请求、异步操作等。
  • 线程池里的线程通常都是 background——它们不会阻止应用退出。

示例:在线程池中运行代码

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "后台 任务");
        Console.WriteLine("Main 正在结束工作。");
        Thread.Sleep(500);
    }

    static void DoWorkInThreadPool(object? state)
    {
        Console.WriteLine("线程池 线程: " + state);
        Thread.Sleep(1000); // 尝试睡眠
        Console.WriteLine("线程池 线程 已完成!");
    }
}

注意点:
如果 Main 比线程池里的工作先结束,线程池线程可能会被中断。如果需要保证完成,使用 foreground 线程或显式等待。

并发的演进:Task,async/await

现在的应用很少直接手动创建 Thread。通常用 Task 和异步方法。要记住:

  • 线程池的线程和 Task 通常运行在 background 线程上。
  • 大多数情况下不需要改优先级——遵循常识和最佳实践就好。

6. 有用的细节

线程的属性和行为

属性 Foreground Thread Background Thread ThreadPool Thread
IsBackground
默认 false true true
应用退出 只要有一个 foreground 线程存活,应用不会退出 如果所有 foreground 线程都结束,应用会退出 只要 Main 结束,应用可能退出
控制 完全控制 完全控制 没有直接控制
常见用途 长期、重要的任务(例如数据库服务器) 非关键任务、后台操作、日志 短任务、Task、异步操作
优先级 可以设置 可以设置 不建议更改

不太明显的技巧和注意事项

  • 只能对普通线程(Thread)修改优先级,不能对池里的任务(Task, ThreadPool)改。
  • 线程池里的线程通常具有 Normal 优先级(不可更改)。
  • Taskasync/await 是更现代的方式,它们把优先级和后台执行的细节“封装”起来了。

7. 关于优先级和线程类型的常见错误

错误 1:滥用优先级。
把所有线程都设为 Highest 或都设为 Lowest 没有实际好处。这会破坏任务执行的平衡,降低应用响应性。

错误 2:忽视后台线程被隐式终止。
如果重要工作(例如保存数据)在后台线程里执行但你不等待它完成,可能会丢失数据。后台线程在进程结束时会被强制中断。

错误 3:对线程优先级期望过高。
线程优先级(Priority)只是给操作系统的建议,而不是保证线程会被优先执行的承诺。

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