






















多线程和并发编程在现代计算机应用中占有至关重要的位置。随着多核处理器的普及,我们的应用程序不仅要支持并发执行,还要高效地管理多线程任务。《CLR via C#》第26章深入探讨了如何在 C# 中使用线程、线程池、任务(Task)以及并行编程库(TPL)来实现高效的并发处理。本文将带你全面了解 C# 中线程和并发编程的实现方式,帮助你写出更高效、更安全的多线程应用。
在操作系统中,线程是最基本的执行单元,能够让计算机同时执行多个任务。Windows 支持线程的核心原因是:
graph LR A[Windows 支持线程] --> B[并发执行任务] B --> C[多核处理器加速] A --> D[响应性提升] A --> E[任务隔离]
每个线程在创建和销毁时都会消耗一定的系统资源,主要体现在:
// 使用线程池执行任务,避免频繁创建和销毁线程
ThreadPool.QueueUserWorkItem(DoWork);
private void DoWork(object state)
{
// 执行任务
}
频繁创建和销毁线程会带来性能瓶颈。为了解决这个问题,可以使用 线程池 来复用线程,而不是每次都创建新的线程。
// 使用线程池执行计算密集型任务
ThreadPool.QueueUserWorkItem(CalculateTask);
private void CalculateTask(object state)
{
// 计算密集型操作
}
随着硬件技术的发展,尤其是 多核 CPU 的普及,线程的作用变得越来越重要。现代计算机通常配备多个核心,线程可以在不同的核心上并行执行任务,从而显著提高程序的处理能力。
graph TD A[多核处理器] --> B[线程并行执行] B --> C[提高计算效率] B --> D[合理分配任务] D --> E[减少上下文切换]
线程是操作系统调度的最小单位,它代表了程序执行的独立路径。每个应用程序都有一个主线程,程序中的其他任务可以在多个线程中并行执行,从而实现并发处理。C# 为开发者提供了 System.Threading 命名空间,允许开发者创建、管理和同步线程。
线程的基本创建过程相对简单,可以使用 Thread 类来启动一个新的线程。
Thread t = new Thread(new ThreadStart(SomeMethod));
t.Start();
ThreadStart 是一个委托,指向一个方法,该方法将在新线程上执行。线程一旦启动,就开始执行指定的任务。
ThreadPool 类允许 C# 管理一组线程,在需要时复用它们,而无需频繁创建和销毁线程。线程池是通过多线程来优化性能的关键技术,避免了传统线程创建的开销。
ThreadPool.QueueUserWorkItem(WorkMethod);
QueueUserWorkItem 方法将指定的任务添加到线程池队列中。线程池的线程会自动取出任务并执行。这种方式非常适用于处理大量短小的并行任务。
随着并行编程需求的增长,C# 提供了 Task 类和 Parallel 类来简化并行操作。
Task 是 .NET 的并发编程核心,用于执行后台任务、异步操作以及并行计算。与传统的线程模型不同,Task 代表了一个异步操作,而不是直接表示一个线程。
Task.Run(() => {
Console.WriteLine("Running in background thread");
});
使用 Task 类可以轻松地实现异步任务的执行,并且在任务完成时可以继续处理结果。Task.WhenAll 可以并行执行多个任务并等待它们全部完成。
Task[] tasks = new Task[3];
for (int i = 0; i < 3; i++) {
tasks[i] = Task.Run(() => {
Console.WriteLine($"Task {i} completed");
});
}
Task.WhenAll(tasks).Wait(); // 等待所有任务完成
Parallel 类提供了更加简便的并行处理方法,常用于需要并行执行相同操作的场景。
Parallel.For(0, 10, i => {
Console.WriteLine($"Processing {i}");
});
Parallel.For 和 Parallel.ForEach 方法用于并行处理循环和集合,能够显著提升性能,尤其是处理大量独立任务时。
多线程编程常见的问题是数据竞态(Race Condition)。当多个线程并发访问共享数据时,如果没有适当的同步控制,就可能导致数据不一致。
C# 提供了 lock 关键字用于简化线程同步,防止多个线程同时进入临界区。
private static readonly object lockObj = new object();
public void SafeMethod() {
lock (lockObj) {
// 线程安全的操作
}
}
lock 确保了只有一个线程能够进入临界区,其他线程需要等待释放锁才能执行。
除了 lock,C# 还提供了多种同步机制:
通过这些同步工具,开发者可以有效地管理线程之间的共享资源。
对于需要在多线程环境中频繁操作的集合,.NET 提供了线程安全的集合类,诸如 ConcurrentQueue<T> 和 ConcurrentDictionary<K, V>。这些集合类在设计时考虑了并发问题,避免了开发者手动同步的复杂性。
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
queue.Enqueue(42);
queue.TryDequeue(out int result);
这些集合类在并发操作时具有更好的性能,特别是在多线程环境中。
在复杂的并发程序中,任务取消和异常管理非常重要。C# 提供了 CancellationToken 来优雅地取消任务,避免任务无限期运行。
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task.Run(() => {
for (int i = 0; i < 10; i++) {
if (token.IsCancellationRequested) {
Console.WriteLine("Task was canceled.");
break;
}
Console.WriteLine(i);
Thread.Sleep(1000);
}
});
cts.Cancel(); // 请求取消任务
在并行任务中,异常管理至关重要。Task 类提供了 ContinueWith 和 WhenAll 方法来处理多个任务的异常。
Task.Run(() => {
throw new InvalidOperationException("Error in task");
}).ContinueWith(task => {
Console.WriteLine($"Task failed with: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
CancellationToken 控制任务执行,可以中断长时间运行的任务。try-catch 块捕获异步任务中的异常,并进行适当的错误处理。Task 和线程池进行任务调度。| 特性 | 说明 |
|---|---|

Q1:如何在 Unity 中避免主线程被阻塞,提升响应性?
async/await 异步编程,或者通过后台线程(如 Thread 或 Task)执行耗时操作,避免主线程阻塞。Q2:如何在 Unity 中管理多个线程?
ThreadPool)或并行任务(Task)来管理多个线程,避免频繁创建和销毁线程。Q3:Unity 的主线程与后台线程有什么不同?
Q4:如何在 Unity 中使用后台线程进行数据处理?
Thread 或 Task 来执行数据处理任务,并确保后台线程不直接访问 Unity 的对象和 API。Q5:在 Unity 中使用线程时应该注意哪些问题?
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。