C# 线程
上次修改时间:2025 年 5 月 12 日
本教程涵盖 C# 中的线程,并解释了进程和线程在程序执行中的运作方式。
进程是正在运行的程序的实例,它包含自己分配的内存和系统资源。 当进程启动时,公共语言运行时 (CLR) 会自动创建一个主前台线程来执行应用程序代码。 一个进程可以包含多个线程,每个线程代表程序中不同的执行路径。
在 C# 中,Thread
类是 System.Threading
命名空间的一部分,提供了创建和管理线程的功能。 它可以设置优先级、控制执行和检索线程状态,从而实现高效的多任务处理和并发执行。
进程和线程是独立的执行序列,但它们在内存使用和性能特征方面有所不同。 下表重点介绍了进程和线程之间的主要区别
进程 | 线程 |
---|---|
在单独的内存空间中运行(进程隔离) | 与同一进程中的其他线程共享内存 |
消耗更多内存 | 消耗更少的内存 |
资源管理开销更高 | 开销更低,更轻量级 |
创建和终止速度较慢 | 创建和终止速度更快 |
更稳定,但需要进程间通信 | 并行执行效率高,但可能需要同步机制 |
由于单独的内存空间,更易于调试 | 由于共享内存和竞争条件,调试可能更复杂 |
C# 线程启动
Start
方法启动一个线程。
Console.WriteLine("main started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}"); var t = new Thread(task); t.Start(); Console.WriteLine("main finished"); void task() { Console.WriteLine("thread started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id}"); }
创建一个新线程,然后使用 Start
启动。
Console.WriteLine("main started");
主程序本身就是一个单独的执行线程。
var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}");
Thread.CurrentThread.ManagedThreadId
获取当前托管线程的唯一标识符。
var t = new Thread(task);
创建一个新的 Thread
。 我们传递对在线程中执行的函数的引用。
t.Start();
线程已启动。
void task() { Console.WriteLine("thread started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id}"); }
在函数内部,我们获取并打印线程 ID。
$ dotnet run main started main id: 1 main finished thread started thread id: 4
请注意,主线程在其他线程之前完成。 程序等待线程完成。
C# 线程传递参数
在下一个示例中,我们将展示如何将参数传递给线程。
int n = 5; string word = "falcon"; // var t = new Thread(() => { for (int i = 0; i < n; i++) Console.WriteLine(word); }); var t = new Thread(() => repeat(n, word)); t.Start(); void repeat(int n, string word) { for (int i = 0; i < n; i++) { Console.WriteLine(word); } }
线程将一个单词打印 n 次。 我们将单词和数字作为参数传递。
var t = new Thread(() => repeat(n, word));
线程采用 lambda 表达式,其中使用两个参数调用 repeat 函数。
$ dotnet run falcon falcon falcon falcon falcon
C# Thread.Sleep
Tread.Sleep
方法将当前线程挂起指定的毫秒数。 该方法对于调试和测试很有用。
它通常用于模拟长时间运行的任务。
for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); var t = new Thread(() => task(n)); t.Start(); } void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); }
在该程序中,我们创建五个线程,这些线程休眠随机毫秒数。
var n = new Random().Next(500, 1500);
我们创建一个介于 500 到 1500 之间的随机数。
var t = new Thread(() => task(n)); t.Start();
我们将随机数传递给新创建的线程。
void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); }
在线程中运行的函数使用 Tread.Sleep
将其执行挂起 n 毫秒。
$ dotnet run thread id: 5 started thread id: 6 started thread id: 4 started thread id: 7 started thread id: 8 started thread id: 5 finished in 822 ms thread id: 8 finished in 891 ms thread id: 6 finished in 902 ms thread id: 4 finished in 946 ms thread id: 7 finished in 1113 ms
C# 前台线程 & 后台线程
有两种类型的线程:前台线程和后台线程。 后台线程不会阻止进程终止。 当属于一个进程的所有前台线程都终止后,CLR 就会结束该进程。
默认线程是前台线程。 我们使用 IsBackground
属性将线程更改为后台线程。
Console.WriteLine("started main"); for (var i = 0; i < 5; i++) { var rn = new Random().Next(500, 1500); var t = new Thread(() => task(rn)); t.IsBackground = true; t.Start(); } void task(int n) { Thread.Sleep(n); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"{id} finished in {n} ms"); } Console.WriteLine("finished main");
在此示例中,我们创建一个主程序线程和五个后台线程。
$ dotnet run started main finished main
一旦唯一的前台线程完成,所有其他后台线程都将终止,并且程序完成。 后台线程没有时间运行。
$ dotnet run started main finished main 8 finished in 572 ms 4 finished in 770 ms 7 finished in 772 ms 6 finished in 1145 ms 5 finished in 1397 ms
当我们注释掉 t.IsBackground = true;
行时,我们创建前台线程。 然后,主线程等待其他前台线程完成,并且运行这五个线程。
C# 线程 Join
Join
方法阻止调用线程,直到指定的线程终止。
Console.WriteLine("main started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}"); Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); threads[i] = new Thread(() => task(n)); } foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); } Console.WriteLine("main finished");
在此程序中,主线程等待所有其他线程完成。
Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); threads[i] = new Thread(() => task(n)); }
我们创建一个包含五个线程的数组,这些线程将休眠随机毫秒数。
foreach (var thread in threads) { thread.Start(); }
首先,我们启动所有五个线程。
foreach (var thread in threads) { thread.Join(); }
使用 Join
,我们阻止主线程,直到数组中的所有五个线程都完成。
$ dotnet run main started main id: 1 thread id: 4 started thread id: 5 started thread id: 6 started thread id: 7 started thread id: 8 started thread id: 7 finished in 802 ms thread id: 4 finished in 1080 ms thread id: 8 finished in 1354 ms thread id: 6 finished in 1358 ms thread id: 5 finished in 1461 ms main finished
C# 线程与 Stopwatch
使用 Stopwatch
,我们可以准确地测量经过的时间。
using System.Diagnostics; Console.WriteLine(Thread.CurrentThread.ManagedThreadId); var sw = new Stopwatch(); sw.Start(); Thread[] threads = new Thread[10]; for (var i = 0; i < 10; i++) { var t = new Thread(() => { var id = Thread.CurrentThread.ManagedThreadId; var r = new Random().Next(500, 1500); Thread.Sleep(r); Console.WriteLine($"{id} finished in {r} ms"); } ); threads[i] = t; } foreach (var t in threads) { t.Start(); } foreach (var t in threads) { t.Join(); } sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms");
我们创建十个线程,这些线程运行随机毫秒数。 主线程等待直到所有其他线程完成,然后计算经过的时间。
var sw = new Stopwatch(); sw.Start();
我们创建 Stopwatch
并运行它。
sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms");
最后,我们计算经过的时间并打印结果。
$ dotnet run 1 13 finished in 539 ms 4 finished in 547 ms 9 finished in 617 ms 6 finished in 782 ms 8 finished in 787 ms 7 finished in 917 ms 10 finished in 968 ms 12 finished in 1170 ms 5 finished in 1468 ms 11 finished in 1488 ms elapsed: 1488 ms
程序的运行时间与最长的线程一样长。
来源
在本文中,我们使用了 C# 中的线程。
作者
列出所有 C# 教程。