包含此页的版本:
不含此页的版本:
若要创建并成功运行作业,必须执行以下作:
要在 Unity 中创建作业,请实现IJob接口。您可以使用您的IJob实现以计划与正在运行的任何其他作业并行运行的单个作业。
IJob有一个必需的方法:Execute,每当工作线程运行作业时,Unity 都会调用该作业。
创建作业时,还可以创建JobHandle对于它,需要使用哪些其他方法来引用作业。
重要:没有防止访问非只读或可变您可以更改可变包的内容。这是不可变的反义词。只有本地包和嵌入式包是可变的。
请参阅术语表作业中的静态数据。访问此类数据会规避所有安全系统,并可能导致您的应用程序或 Unity 编辑器崩溃。
当 Unity 运行时,作业系统会复制计划的作业数据,从而防止多个线程读取或写入相同的数据。仅写入NativeContainer可以在作业完成后访问。这是因为NativeContainer作业使用的和原始NativeContainer对象指向同一内存。有关详细信息,请参阅有关线程安全类型的文档。
当作业系统从其作业队列中选取作业时,它会运行Execute方法在单个线程上一次。通常,作业系统在后台线程上运行作业,但如果主线程处于空闲状态,它可以选择主线程。因此,您应该将作业设计为在框架下完成。
要计划作业,请调用Schedule.这会将作业放入作业队列中,一旦作业的所有依赖项(如果有)完成,作业系统就会开始执行作业。一旦安排,就无法中断作业。您只能调用Schedule从主线程。
提示:作业有一个Run您可以使用的方法代替Schedule立即在主线程上执行作业。您可以将其用于调试目的。
一旦你调用Schedule并且作业系统已经执行了作业,可以调用Complete方法JobHandle以访问作业中的数据。最佳做法是调用Complete尽可能晚。当您调用Complete,主线程可以安全地访问NativeContainer作业正在使用的实例。叫Complete还清理了安全系统中的状态。不这样做会导致内存泄漏。
下面是将两个浮点值相加的作业示例。它实现IJob,使用NativeArray获取作业结果,并使用Execute方法,其中包含其中作业的实现:
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
// 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;
}
}
以下示例基于MyJobjob 在主线程上调度作业:
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
public class MyScheduledJob : MonoBehaviour
{
// Create a native array of a single float to store the result. Using a
// NativeArray is the only way you can get the results of the job, whether
// you're getting one value or an array of values.
NativeArray<float> result;
// Create a JobHandle for the job
JobHandle handle;
// Set up the job
public struct MyJob : IJob
{
public float a;
public float b;
public NativeArray<float> result;
public void Execute()
{
result[0] = a + b;
}
}
// Update is called once per frame
void Update()
{
// Set up the job data
result = new NativeArray<float>(1, Allocator.TempJob);
MyJob jobData = new MyJob
{
a = 10,
b = 10,
result = result
};
// Schedule the job
handle = jobData.Schedule();
}
private void LateUpdate()
{
// Sometime later in the frame, wait for the job to complete before accessing the results.
handle.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();
}
}
最佳做法是调用Schedule在作业上获得所需的数据后立即调用,并且不要调用Complete直到您需要结果。
您可以在框架中不与更重要的工作竞争的部分安排不太重要的工作。
例如,如果一帧结束和下一帧开始之间有一段时间没有作业正在运行,并且可以接受一帧延迟,则可以将作业安排在帧结束,并在下一帧中使用其结果。或者,如果您的应用程序使该转换期与其他作业相匹配,并且框架中的其他位置有一个未充分利用的时期,那么将您的作业安排在那里会更有效。
您还可以使用分析器帮助您优化游戏的窗口。它显示了在游戏的各个领域花费了多少时间。例如,它可以报告渲染、动画制作或游戏逻辑所花费的时间百分比。更多信息
请参阅术语表查看 Unity 在哪里等待作业完成。标记WaitForJobGroupID在主线程上表明了这一点。此标记可能意味着你在某处引入了应该解决的数据依赖关系。查找JobHandle.Complete以追踪强制主线程等待的数据依赖关系。
与线程不同,作业不产生执行。作业启动后,该作业工作线程将承诺在运行任何其他作业之前完成作业。因此,最佳做法是将长时间运行的作业分解为相互依赖的较小作业,而不是提交相对于系统中其他作业需要很长时间才能完成的作业。
作业系统通常运行多个作业依赖项链,因此,如果将长时间运行的任务分解为多个部分,则多个作业链就有可能取得进展。相反,如果作业系统充满了长时间运行的作业,它们可能会完全消耗所有工作线程并阻止独立作业执行。这可能会推送主线程显式等待的重要作业的完成时间,从而导致主线程上停滞,否则这些停滞不会存在。
特别是,长时间运行IJobParallelFor作业会对作业系统产生负面影响,因为这些作业类型有意尝试在尽可能多的工作线程上运行,以实现作业批处理大小。如果无法分解长时间的并行作业,请考虑在计划作业时增加作业的批大小,以限制选择长时间运行作业的辅助角色数量。
MyParallelJob jobData = new MyParallelJob();
jobData.Data = someData;
jobData.Result = someArray;
// Use half the available worker threads, clamped to a minimum of 1 worker thread
const int numBatches = Math.Max(1, JobsUtility.JobWorkerCount / 2);
const int totalItems = someArray.Length;
const int batchSize = totalItems / numBatches;
// Schedule the job with one Execute per index in the results array and batchSize items per processing batch
JobHandle handle = jobData.Schedule(result.Length, totalItems, batchSize);