C#Job System如何工作(8)-JobHandle和依赖关系

本文深入讲解Unity中的Job系统,包括JobHandle的使用、依赖关系管理、依赖合并以及如何在主线程中等待Job执行完成。通过具体示例展示了如何创建、调度Job以及处理NativeArray数据。

原文:https://docs.unity3d.com/Manual/JobSystemJobDependencies.html

JobHandle和依赖关系

JobHandle and dependencies

当您调用作业的Schedule方法时,它将返回JobHandle。您可以在代码中使用JobHandle 作为其他Job的依赖关系。如果Job取决于另一个Job的结果,您可以将第一个作业JobHandle作为参数传递给第二个作业的Schedule方法,如下所示:

JobHandle firstJobHandle = firstJob.Schedule();
secondJob.Schedule(firstJobHandle);

结合依赖关系

如果作业有许多依赖项,则可以使用JobHandle.CombineDependencies方法合并它们。CombineDependencies允许您将它们传递给Schedule方法。

NativeArray<JobHandle> handles = new NativeArray<JobHandle>(numJobs, Allocator.TempJob);

// Populate `handles` with `JobHandles` from multiple scheduled jobs...

JobHandle jh = JobHandle.CombineDependencies(handles);

在主线程中等待Job

在主线程中使用JobHandle强迫让你的代码等待您的Job执行完毕。要做到这一点,调用JobHandle的方法 Complete。此时,您知道主线程可以安全地访问正在使用job 的NativeContainer。

注意:在调度Job时,Job不会开始执行。如果您正在等待主线程中的Job,并且您需要访问正在使用Job的NativeContainer数据,则可以调用该方法JobHandle.Complete。此方法从内存高速缓存中刷新作业并启动执行过程。调用JobHandle的Complete方法将返回NativeContainer的所有权到主线程。您需要再次调用 JobHandle 的Complete方法以便于再次从主线程安全地访问这些NativeContainer类型。也可以通过从Job的依赖中的JobHandle的Complete方法调用返回主线程上的所有权。例如,你可以调用jobA的Complete方法,或者也可以调用依靠JobA的JobB上的Complete方法。两者都会在调用Complete后在主线程上安全访问时使用jobA的NativeContainer类型。

否则,如果您不需要访问数据,则需要明确刷新批处理。为此,请调用静态方法JobHandle.ScheduleBatchedJobs。请注意,调用此方法可能会对性能产生负面影响。

多个Jobs和dependencies的示例

Job code:
// Job adding two floating point values together
public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

// Job adding one to a value
public struct AddOneJob : IJob
{
    public NativeArray<float> result;
    
    public void Execute()
    {
        result[0] = result[0] + 1;
    }
}

主线程代码:
// Create a native array of a single float to store the result in. This example waits for the job to complete
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

// Setup the data for job #1
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

// Schedule job #1
JobHandle firstHandle = jobData.Schedule();

// Setup the data for job #2
AddOneJob incJobData = new AddOneJob();
incJobData.result = result;

// Schedule job #2
JobHandle secondHandle = incJobData.Schedule(firstHandle);

// Wait for job #2 to complete
secondHandle.Complete();

// All copies of the NativeArray point to the same memory, you can access the result in "your" copy of the NativeArray
float aPlusB = result[0];

// Free the memory allocated by the result array
result.Dispose();
Unity 的 **C# Job System** 是一种用于优化性能的多线程任务处理系统,它允许开发者将计算任务分割为多个 **Job**(作业),并在多个 CPU 核心上并行执行。由于 Unity 引擎自动管理线程池同步机制,开发者可以专注于任务逻辑的实现,而无需处理底层的线程管理[^2]。 ### 核心组件与概念 1. **Job**:Job 是一个轻量级的任务,它可以在多个线程上并行执行。Job 需要实现 `IJob` 接口或其派生接口(如 `IJobParallelFor`、`IJobParallelForTransform`)。 2. **NativeContainer**:为了确保 Job 安全地访问数据,Unity 提供了 `NativeArray<T>`、`NativeList<T>` 等原生容器,这些容器支持跨线程访问且不会产生垃圾回收(GC)负担。 3. **JobHandle**:每个提交的 Job 都会返回一个 `JobHandle` 对象,它用于跟踪 Job 的执行状态,并支持依赖链的构建,确保 Job 按照正确的顺序执行。 4. **Scheduling**:Job 的调度由 Unity 自动管理,开发者可以通过 `JobHandle` 来控制调度顺序,实现依赖关系。 ### 基本使用流程 以下是一个使用 `IJobParallelFor` 的简单示例,展示如何在多个元素上并行执行操作: ```csharp using Unity.Burst; using Unity.Jobs; using Unity.Collections; using UnityEngine; public class JobExample : MonoBehaviour { void Start() { int length = 1000; NativeArray<float> values = new NativeArray<float>(length, Allocator.TempJob); // 定义一个 Job var job = new MyJob { Values = values }; // 调度 Job JobHandle handle = job.Schedule(length, 64); // 64 为批处理大小 // 等待 Job 完成 handle.Complete(); // 读取结果 for (int i = 0; i < length; i++) { Debug.Log(values[i]); } values.Dispose(); // 释放原生容器 } [BurstCompile] struct MyJob : IJobParallelFor { public NativeArray<float> Values; public void Execute(int index) { Values[index] = Mathf.Sin(index * 0.1f); } } } ``` ### 最佳实践 1. **合理划分 Job 粒度**:Job 的粒度不宜过小,否则调度开销可能超过实际计算收益。建议将任务划分为多个中等大小的 Job,以充分利用 CPU 资源。 2. **使用 Burst 编译器**:通过 `[BurstCompile]` 属性可以显著提升 Job 的执行效率,Burst 将 C# 编译为高度优化的本地代码[^1]。 3. **避免主线程阻塞**:尽量避免频繁调用 `JobHandle.Complete()`,特别是在 Update 方法中。可以使用依赖链来异步处理多个 Job。 4. **使用 NativeContainer 管理数据**:确保所有在 Job 中访问的数据都封装在 `NativeArray`、`NativeList` 等容器中,以避免内存安全问题。 5. **控制调度频率**:对于频繁执行的小型任务,建议使用 `IJobParallelFor` 并设置合适的批处理大小(batch size),通常 32 到 1024 之间较为合适。 ### 多线程与依赖管理 Unity Job System 支持 Job 之间的依赖关系管理。例如: ```csharp JobHandle firstJobHandle = firstJob.Schedule(...); JobHandle secondJobHandle = secondJob.Schedule(..., firstJobHandle); secondJobHandle.Complete(); ``` 上述代码确保了 `secondJob` 在 `firstJob` 完成之后才开始执行。这种机制非常适用于流水线式的任务处理逻辑。 ### 性能调优建议 - **分析 Job 执行时间**:使用 Unity 的 Profiler 工具监控 Job 的执行时间,识别瓶颈。 - **减少主线程与 Job 之间的数据同步**:避免频繁地从主线程读取 Job 结果,尽可能在 Job 中完成数据处理。 - **合理使用 Allocator.TempJob**:该分配器专为 Job 设计,生命周期短,适合临时数据使用。 ### 常见问题与注意事项 - **GC 内存不能在 Job 中使用**:所有在 Job 中使用的数据结构必须为非托管类型或使用 `NativeContainer`。 - **避免竞态条件**:多个 Job 同时写入同一数据会导致未定义行为,应使用适当的同步机制或设计无共享数据的 Job- **注意 Job 的执行顺序**:除非显式设置依赖关系Job 的执行顺序是不确定的。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值