Skip to content

多线程

更新: 7/30/2025 字数: 0 字 时长: 0 分钟

在如今的C#异步编程开发中,Task 是绝对的主流,不需要把 Thread 类学得像 Task 那样细致入微,了解即可。

推荐视频:C#多线程入门概念及技巧

创建线程

最常用的创建方法是使用 new Thread(ThreadStart start) ,由于 ThreadStart 是一个无参无返回值的委托,所以这里可以直接声明一个函数传入:

c#
var thread = new Thread(() => Console.WriteLine("Thread is running..."));
thread.Start();

通过 new Thread() 创建的线程默认是前台线程。

切换为后台线程

在 C# 中,只要进程中至少还有一个前台线程正在运行,整个 .NET 应用程序(进程)就不会完全终止。即使 Main 方法(主线程)已经执行完毕。

但后台线程不会阻止应用程序终止,所有前台线程都结束执行(通常是 Main 方法退出)时,运行时环境 (CLR) 会立即停止所有仍在运行的后台线程,无论它们是否执行完毕。

可以通过将线程的 IsBackground 属性设置为 true 来将其标记为后台线程:

c#
var thread = new Thread(() => Console.WriteLine("Thread is running..."));
thread.IsBackground = true;
thread.Start();

与 java 不同,线程在 Start 之前设置为后台线程不是必要的。

线程休眠

使用 Thread 类的 Sleep(int millisecondsTimeout) 方法即可:

c#
Thread.Sleep(1000);

终止死循环线程

通过 try-catch 环绕包裹死循环的代码,其中要包含 Thread.Sleep 方法。然后在外部调用 Thread 实例的 Interrupt() 方法,代码执行到 Sleep 处时会抛出一个线程中断异常,此时如果try-catch 后没有代码逻辑,线程执行就会停止:

c#
var thread = new Thread(() =>
{
    try
    {
        while (true)
        {
            Thread.Sleep(0); // 在这里抛出异常
            Console.WriteLine("Thread is running...");
        }
    }
    catch (ThreadInterruptedException)
    {
        Console.WriteLine("Thread is interrupted.");
    }
});
thread.Start();
thread.Interrupt();

我们会在之后介绍 Task 的时候讲解更好的终止线程的方法(协作式取消)。

DANGER

首要原则:避免强制终止 ( Thread.Abort )

在早期版本的 .NET 中,有一个 Thread.Abort() 方法。它的作用是试图强行向目标线程注入一个异常( ThreadAbortException ),以期能立即杀死它。

请绝对不要在你的代码中使用它。 它可能在线程执行任何指令的瞬间被注入。如果线程当时正在一个 finally 块中释放关键资源(如文件句柄、数据库连接),或者正在修改一个重要的数据结构,那么强制终止将导致资源无法释放、数据结构被破坏,使你的程序处于一种“已损坏”的状态。这相当于直接切断电源,而不是正常关机。你无法预料会发生什么,也无法给线程一个“收拾残局”的机会。

等待线程结束

thread.Join() 方法会阻塞当前线程,直到 thread 目标线程执行完毕。

Mission.cs
c#
var thread = new Thread(() => Thread.Sleep(2000));
thread.Start();

thread.Join();

lock锁

通过 lock 关键字可以确保在同一时间只有一个线程可以访问某个代码块或资源。它会自动处理线程间的同步问题,防止多个线程同时修改同一数据导致的数据不一致。

c#
private static readonly object _lock = new object();

public void SafeMethod()
{
    lock (_lock)
    {
        // 只有一个线程可以进入这个代码块
        Console.WriteLine("Thread is working...");
        Thread.Sleep(1000);
    }
}

信号量

信号量( Semaphore )是一种用于控制对共享资源访问的同步原语。它允许多个线程同时访问一个或多个资源,但限制同时访问的线程数量。

c#
Semaphore semaphore = new Semaphore(2, 2); // 最多允许2个线程同时访问

public void AccessResource()
{
    semaphore.WaitOne(); // 请求信号量
    try
    {
        // 访问共享资源
        Console.WriteLine("Thread is accessing the resource...");
        Thread.Sleep(1000);
    }
    finally
    {
        // 释放信号量
        semaphore.Release();
    } 
}

原子操作

使用 Interlocked 类可以执行原子操作。

c#
Interlocked.Increment(ref _counter);

线程安全的集合

C# 提供了一些线程安全的集合类,如 ConcurrentDictionaryConcurrentQueue,它们可以在多线程环境中安全地使用。

c#
var queue = new ConcurrentQueue<int>();

var producer = new Thread(AddNumber);
var consumer1 = new Thread(ReadNumber);
var consumer2 = new Thread(ReadNumber);

producer.Start();
consumer1.Start();
consumer2.Start();

producer.Join();
consumer1.Interrupt();
consumer2.Interrupt();
consumer1.Join();
consumer2.Join();

void AddNumber()
{
	for (int i = 0; i < 10; i++)
	{
		Thread.Sleep(20); 
		queue.Enqueue(i);
	}
}

void ReadNumber()
{
	try 
	{
		while (true)
		{
			if (queue.TryDequeue(out int number))
			{
				Console.WriteLine($"Dequeued: {number}");
			}
			Thread.Sleep(1); 
		}
	}
	catch (ThreadInterruptedException)
	{
		Console.WriteLine("Interrupted.");
	}
}

TIP

C# 中提供了许多解决多线程问题的工具和方法,没必要自己造轮子,多考虑阅读官方文档,使用原生的方法。