1. 介绍
把线程想像成排队买冰淇淋的人。有些人安静地站着,有些人会吵嚷("我快赶不上火车了,请让我先!"),店员似乎能听见所有人,但有时候会让某些人插队——比如抱着哭闹孩子的妈妈。线程优先级大概就是这么回事。
在 C#(确切说是 .NET)里,每个线程都有优先级——这是给操作系统的一个提示,表示这个线程相对于其他线程“更重要”。这不是对操作系统的强制命令(没人保证最高优先级的线程总能拿到全部注意力),但通常高优先级线程会得到更多的 CPU 时间。
这在什么场景真正有用?
- 在 UI:比如屏幕更新不应该被低优先级的重计算拖慢。
- 在游戏里:渲染帧比后台的地图生成更重要。
- 在对响应时间有严格要求的任务:设备控制、信号处理等。
2. 线程优先级:工作原理
在 C# 里处理线程优先级很简单——Thread 对象有一个 Priority 属性。它接受来自枚举 ThreadPriority 的值:
| 值 | 说明 |
|---|---|
|
最低优先级 |
|
低于中等 |
|
普通(默认) |
|
高于中等 |
|
最高优先级 |
代码示例:
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 就行了,但那是个坏主意!想象一下,如果商店里所有顾客同时喊 "先服务我!"。
只有在有理由的时候才改优先级:
- 日志写入的后台线程——可以设为 BelowNormal 或 Lowest。
- 处理用户输入的线程——设为 AboveNormal。
- 不需要及时完成的 CPU 密集任务——低优先级。
小技巧: 如果应用变慢了,检查有没有“嚣张”的高优先级线程在抢资源,影响了其他工作!
4. .NET 中的线程类别
在 C# 里通常把线程分成两类:foreground 和 background。有意思的是,"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 |
|---|---|---|---|
|
默认 false | true | true |
| 应用退出 | 只要有一个 foreground 线程存活,应用不会退出 | 如果所有 foreground 线程都结束,应用会退出 | 只要 Main 结束,应用可能退出 |
| 控制 | 完全控制 | 完全控制 | 没有直接控制 |
| 常见用途 | 长期、重要的任务(例如数据库服务器) | 非关键任务、后台操作、日志 | 短任务、Task、异步操作 |
| 优先级 | 可以设置 | 可以设置 | 不建议更改 |
不太明显的技巧和注意事项
- 只能对普通线程(Thread)修改优先级,不能对池里的任务(Task, ThreadPool)改。
- 线程池里的线程通常具有 Normal 优先级(不可更改)。
- Task 和 async/await 是更现代的方式,它们把优先级和后台执行的细节“封装”起来了。
7. 关于优先级和线程类型的常见错误
错误 1:滥用优先级。
把所有线程都设为 Highest 或都设为 Lowest 没有实际好处。这会破坏任务执行的平衡,降低应用响应性。
错误 2:忽视后台线程被隐式终止。
如果重要工作(例如保存数据)在后台线程里执行但你不等待它完成,可能会丢失数据。后台线程在进程结束时会被强制中断。
错误 3:对线程优先级期望过高。
线程优先级(Priority)只是给操作系统的建议,而不是保证线程会被优先执行的承诺。
GO TO FULL VERSION