Version: 2019.1
C# 作业系统中的安全系统
创建作业

NativeContainer

安全系统复制数据的过程的缺点是会将作业的结果隔离到每个副本中。为了克服此限制,需要将结果存储在一种名为 NativeContainer 的共享内存中。

NativeContainer 是什么?

NativeContainer 是一种托管值类型,为本机内存提供了一个相对安全的 C# 封装器。它包含一个指向非托管分配的指针。与 Unity C# 作业系统一起使用时,NativeContainer 允许作业访问与主线程共享的数据,而不是使用副本。

有哪些可用的 NativeContainer 类型?

Unity 附带了一个名为 NativeArrayNativeContainer。您也可以使用 NativeSlice 操作 NativeArray,从特定位置获取特定长度的 NativeArray 子集。

注意实体组件系统 (ECS) 包可以扩展 Unity.Collections 命名空间以包含其他类型的 NativeContainer

  • NativeList - 可调整大小的 NativeArray
  • NativeHashMap - 键/值对。
  • NativeMultiHashMap - 每个键有多个值。
  • NativeQueue - 先进先出 (FIFO) 队列。

NativeContainer 和安全系统

安全系统内置于所有 NativeContainer 类型中。此系统会跟踪在 NativeContainer 中读写的内容。

注意:对 NativeContainer 类型进行的所有安全检查(例如越界检查、取消分配检查和竞争条件检查)仅在 Unity EditorPlay Mode 中可用。

该安全系统包含 DisposeSentinelAtomicSafetyHandleDisposeSentinel 可检测内存泄漏,如果未正确释放内存,则会报错。内存泄漏发生很长时间后才会触发内存泄漏错误。

使用 AtomicSafetyHandle 可以在代码中转移 NativeContainer 的所有权。例如,如果两个调度的作业正在写入相同的 NativeArray,则安全系统会抛出一个异常,并显示一条明确的错误消息,说明问题的原因和解决方法。调度违规的作业时,安全系统会抛出此异常。

在这种情况下,可以调度具有依赖关系的作业。第一个作业可以写入 NativeContainer,一旦该作业完成执行,下一个作业就可以安全地读取和写入相同的 NativeContainer。从主线程访问数据时,读写限制也适用。安全系统允许多个作业并行读取相同的数据。

默认情况下,当作业可以访问 NativeContainer 时,该作业便具有读写访问权限。此配置可能会降低性能。C# 作业系统不允许在一个作业正在写入 NativeContainer 的同时再调度另一个作业对其进行写访问。

如果作业不需要写入 NativeContainer,请用 [ReadOnly] 属性标记 NativeContainer,如下所示:

[ReadOnly]
public NativeArray<int> input;

在上面的示例中,允许多个作业同时对 NativeArray 进行只读访问。

注意:无法防止从作业中访问静态数据。访问静态数据时会绕过所有安全系统,并可能导致 Unity 崩溃。如需了解更多信息,请参阅 C# 作业系统提示和故障排除

NativeContainer Allocator

创建 NativeContainer 时,必须指定所需的内存分配类型。分配类型取决于作业运行的时间长度。因此,可以定制分配以便在每种情况下获得最佳性能。

可以使用三种 Allocator 类型进行 NativeContainer 内存分配和释放。在实例化 NativeContainer 时需要指定适当的一种类型。

  • Allocator.Temp 具有最快的分配速度。此类型适用于寿命为一帧或更短的分配。不应该使用 TempNativeContainer 分配传递给作业。在从方法调用(例如 MonoBehaviour.Update 或从本机代码到托管代码的任何其他回调)返回之前,还需要调用 Dispose 方法。
  • Allocator.TempJob 的分配速度比 Temp 慢,但比 Persistent 快。此类型适用于寿命为四帧的分配,并具有线程安全性。如果没有在四帧内对其执行 Dispose 方法,控制台会输出一条从本机代码生成的警告。大多数小作业都使用这种 NativeContainer 分配类型。
  • Allocator.Persistent 是最慢的分配,但可以在您所需的任意时间内持续存在,如果有必要,可以在整个应用程序的生命周期内存在。此分配器是直接调用 malloc 的封装器。持续时间较长的作业可以使用这种 NativeContainer 分配类型。在非常注重性能的情况下不应使用 Persistent

例如:

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

注意:上例中的数字 1 表示 NativeArray 的大小。在此例子中只有一个数组元素(因为只会在 result 中存储一段数据)。


  • 2018–06–15 页面已发布并进行了编辑审查

  • 2018.1 版中公开了 C# 作业系统 NewIn20181

C# 作业系统中的安全系统
创建作业