安全系统复制数据的过程的缺点是会将作业的结果隔离到每个副本中。为了克服此限制,需要将结果存储在一种名为 NativeContainer 的共享内存中。
NativeContainer
是一种托管值类型,为本机内存提供了一个相对安全的 C# 封装器。它包含一个指向非托管分配的指针。与 Unity C# 作业系统一起使用时,NativeContainer
允许作业访问与主线程共享的数据,而不是使用副本。
Unity 附带了一个名为 NativeArray 的 NativeContainer
。您也可以使用 NativeSlice 操作 NativeArray
,从特定位置获取特定长度的 NativeArray
子集。
注意:实体组件系统 (ECS) 包可以扩展 Unity.Collections
命名空间以包含其他类型的 NativeContainer
:
NativeList
- 可调整大小的 NativeArray
。NativeHashMap
- 键/值对。NativeMultiHashMap
- 每个键有多个值。NativeQueue
- 先进先出 (FIFO) 队列。安全系统内置于所有 NativeContainer
类型中。此系统会跟踪在 NativeContainer
中读写的内容。
注意:对 NativeContainer
类型进行的所有安全检查(例如越界检查、取消分配检查和竞争条件检查)仅在 Unity Editor 和 Play Mode 中可用。
该安全系统包含 DisposeSentinel 和 AtomicSafetyHandle。DisposeSentinel
可检测内存泄漏,如果未正确释放内存,则会报错。内存泄漏发生很长时间后才会触发内存泄漏错误。
使用 AtomicSafetyHandle
可以在代码中转移 NativeContainer
的所有权。例如,如果两个调度的作业正在写入相同的 NativeArray
,则安全系统会抛出一个异常,并显示一条明确的错误消息,说明问题的原因和解决方法。调度违规的作业时,安全系统会抛出此异常。
在这种情况下,可以调度具有依赖关系的作业。第一个作业可以写入 NativeContainer
,一旦该作业完成执行,下一个作业就可以安全地读取和写入相同的 NativeContainer
。从主线程访问数据时,读写限制也适用。安全系统允许多个作业并行读取相同的数据。
默认情况下,当作业可以访问 NativeContainer
时,该作业便具有读写访问权限。此配置可能会降低性能。C# 作业系统不允许在一个作业正在写入 NativeContainer
的同时再调度另一个作业对其进行写访问。
如果作业不需要写入 NativeContainer
,请用 [ReadOnly]
属性标记 NativeContainer
,如下所示:
[ReadOnly]
public NativeArray<int> input;
在上面的示例中,允许多个作业同时对 NativeArray
进行只读访问。
注意:无法防止从作业中访问静态数据。访问静态数据时会绕过所有安全系统,并可能导致 Unity 崩溃。如需了解更多信息,请参阅 C# 作业系统提示和故障排除。
创建 NativeContainer
时,必须指定所需的内存分配类型。分配类型取决于作业运行的时间长度。因此,可以定制分配以便在每种情况下获得最佳性能。
可以使用三种 Allocator 类型进行 NativeContainer
内存分配和释放。在实例化 NativeContainer
时需要指定适当的一种类型。
Temp
将 NativeContainer
分配传递给作业。在从方法调用(例如 MonoBehaviour.Update 或从本机代码到托管代码的任何其他回调)返回之前,还需要调用 Dispose
方法。Temp
慢,但比 Persistent
快。此类型适用于寿命为四帧的分配,并具有线程安全性。如果没有在四帧内对其执行 Dispose
方法,控制台会输出一条从本机代码生成的警告。大多数小作业都使用这种 NativeContainer
分配类型。NativeContainer
分配类型。在非常注重性能的情况下不应使用 Persistent
。例如:
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
注意:上例中的数字 1 表示 NativeArray
的大小。在此例子中只有一个数组元素(因为只会在 result
中存储一段数据)。