






















随着计算密集型操作对性能的要求越来越高,多线程编程和异步操作已成为现代应用程序的标准。特别是在计算密集型任务中,如何高效利用 CPU 资源并进行合理的线程调度和任务管理,成为了开发者们面临的重要挑战。《CLR via C#》的第27章聚焦于计算密集型异步操作,提供了一些关键概念与实用技术,帮助我们在 C# 中高效执行并发操作。
在 CLR 中,线程池是处理并发任务的重要工具。它管理线程的生命周期,允许应用程序复用线程,避免了频繁创建和销毁线程的高开销。
线程池的优势:
ThreadPool.QueueUserWorkItem(ComputeTask);
这种方式允许将任务排队到线程池中,线程池会自动选择一个线程来执行该任务。
执行上下文代表线程执行过程中的环境状态(如当前线程的执行栈、同步上下文、文化信息等)。CLR 会在执行任务时自动管理上下文信息,保证线程在任务切换时的状态不丢失。
Thread.CurrentThread 相关,可以保存一些如文化信息、SecurityContext 等信息。ExecutionContext.Run(ExecutionContext.Capture(), new ContextCallback((state) => {
// 执行代码
}), null);
计算密集型任务(例如图像处理、数据分析、加密算法等)通常会占用大量 CPU 时间,导致应用程序的响应性差。为了有效执行计算密集型操作,异步编程与线程池的结合至关重要。
Task.Run 可以轻松将计算密集型任务分配到线程池中。Task.Run(() => {
PerformHeavyComputation();
});
这种方法不仅可以避免主线程被阻塞,还能确保应用程序的响应性。
在执行异步任务时,往往需要有取消和超时的机制,尤其是在执行长时间运行的计算密集型任务时,确保任务可以在合适的时机中止。
CancellationToken 是 C# 提供的协作取消机制,它允许开发者手动取消正在执行的任务。
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task.Run(() => {
for (int i = 0; i < 1000; i++) {
if (token.IsCancellationRequested) {
break;
}
// 执行计算
}
}, token);
可以通过 Task.WhenAny 或 CancellationToken 实现任务的超时控制。
var task = Task.Run(() => PerformHeavyComputation());
if (await Task.WhenAny(task, Task.Delay(5000)) == task) {
// 任务完成
} else {
// 超时
cts.Cancel();
}
Task 是异步操作的核心。Task 提供了易于使用的异步控制流,可以启动、等待并控制并发任务。与线程不同,Task 管理的是异步操作,而非直接的线程。
任务工厂是一个便捷的方式,用于批量创建和启动多个任务,并能统一管理这些任务的生命周期。
TaskFactory factory = new TaskFactory();
Task[] tasks = new Task[3];
for (int i = 0; i < 3; i++) {
tasks[i] = factory.StartNew(() => {
PerformHeavyComputation();
});
}
Task.WaitAll(tasks);
这种方式简化了任务的管理和调度,特别是在高并发环境下非常有用。
C# 中的任务调度器允许开发者指定任务的执行环境。例如,可以创建自定义的任务调度器,将任务限制在特定的线程池中,或者指定任务在特定的时间段执行。
TaskScheduler customScheduler = new CustomTaskScheduler();
Task.Factory.StartNew(() => {
PerformHeavyComputation();
}, CancellationToken.None, TaskCreationOptions.None, customScheduler);
Parallel 类 提供了用于并行执行操作的静态方法:
Parallel.For:并行执行循环。Parallel.ForEach:并行执行集合中的每个元素。Parallel.Invoke:并行执行多个方法。Parallel.For(0, 1000, i => {
Console.WriteLine($"Processing {i}");
});
这些方法让开发者无需手动创建线程即可并行执行多个任务,极大地简化了并发编程。
对于某些需要周期性执行的计算任务,可以使用 Timer 类来控制任务的执行间隔。
Timer timer = new Timer(state => {
PerformHeavyComputation();
}, null, 0, 1000); // 每秒执行一次
Timer 适合处理周期性任务,但要注意避免任务执行时间过长导致定时器频繁触发。
Task 类则简化了异步操作的管理。Parallel.For 和 Parallel.ForEach 提供了非常简洁的并行编程模型,帮助开发者高效利用多核处理器。CancellationToken 和 Task.WhenAny 实现任务取消与超时控制,确保任务按时完成。TaskScheduler 和 TaskFactory,开发者可以更加灵活地控制任务的执行策略,确保并发任务的合理调度。解析:
Unity 的主线程负责渲染、输入处理和大多数 Unity API 的调用,因此应避免阻塞操作。可以通过 Task.Run() 或 线程池 将计算密集型逻辑移出主线程执行,然后通过 Main Thread Dispatcher 或回调机制回到主线程更新 UI。
Task.Run(() => {
var result = HeavyComputation();
UnityMainThreadDispatcher.Enqueue(() => UpdateUI(result));
});
解析:
Unity 的大部分 API 只能在主线程访问。若在其他线程中调用如 Transform, GameObject, UI 等相关 API,可能会引发崩溃或未定义行为。解决方式是:
Dispatcher、Coroutines 或 SynchronizationContext 切回主线程操作 Unity API。SynchronizationContext context = SynchronizationContext.Current;
Task.Run(() => {
var result = Calculate();
context.Post(_ => ApplyToUnityAPI(result), null);
});
解析:
ThreadPool 和 Task 都是适用于计算密集型任务的工具。在 Unity 中使用它们处理不涉及 Unity API 的任务(如路径查找、数据加密)非常合适。
Task.Run(() => {
var path = CalculatePath();
resultReady = true;
});
使用时注意:
Task.WhenAll 聚合多个任务,处理批量并发需求。解析:
| 特性 | 协程 Coroutine | Task / async-await |
|---|---|---|
结合使用示例:
IEnumerator LoadData() {
Task<string> task = Task.Run(() => LoadFromDisk());
yield return new WaitUntil(() => task.IsCompleted);
Debug.Log(task.Result);
}
解析:
通过 CancellationTokenSource 实现取消控制,结合 Task.WhenAny 实现超时逻辑,既保证任务响应性,又避免长时间阻塞。
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token;
var task = Task.Run(() => {
while (!token.IsCancellationRequested) {
// 模拟长任务
}
}, token);
if (await Task.WhenAny(task, Task.Delay(3000)) != task) {
cts.Cancel(); // 超时取消任务
}
注意:
IsCancellationRequested。Coroutine 回主线程展示取消结果。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。