ZetCode

C# 线程

上次修改时间:2025 年 5 月 12 日

本教程涵盖 C# 中的线程,并解释了进程和线程在程序执行中的运作方式。

进程是正在运行的程序的实例,它包含自己分配的内存和系统资源。 当进程启动时,公共语言运行时 (CLR) 会自动创建一个主前台线程来执行应用程序代码。 一个进程可以包含多个线程,每个线程代表程序中不同的执行路径。

在 C# 中,Thread 类是 System.Threading 命名空间的一部分,提供了创建和管理线程的功能。 它可以设置优先级、控制执行和检索线程状态,从而实现高效的多任务处理和并发执行。

进程和线程是独立的执行序列,但它们在内存使用和性能特征方面有所不同。 下表重点介绍了进程和线程之间的主要区别

进程 线程
在单独的内存空间中运行(进程隔离) 与同一进程中的其他线程共享内存
消耗更多内存 消耗更少的内存
资源管理开销更高 开销更低,更轻量级
创建和终止速度较慢 创建和终止速度更快
更稳定,但需要进程间通信 并行执行效率高,但可能需要同步机制
由于单独的内存空间,更易于调试 由于共享内存和竞争条件,调试可能更复杂
表:进程与线程

C# 线程启动

Start 方法启动一个线程。

Program.cs
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# 线程传递参数

在下一个示例中,我们将展示如何将参数传递给线程。

Program.cs
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 方法将当前线程挂起指定的毫秒数。 该方法对于调试和测试很有用。

它通常用于模拟长时间运行的任务。

Program.cs
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 属性将线程更改为后台线程。

Program.cs
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 方法阻止调用线程,直到指定的线程终止。

Program.cs
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,我们可以准确地测量经过的时间。

Program.cs
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

程序的运行时间与最长的线程一样长。

来源

Thread 类 - 语言参考

在本文中,我们使用了 C# 中的线程。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程