C#和Unity中的异步探究

 

C#自早期版本引入Async/Await语法糖,其背后实现其实是Task。对于Unity也引入了这样的语法糖来大大方便了异步实现,但对比传统的C#工程,和使用C#作为脚本语言的Unity,他们之间的异步仍然是有区别的。

Unity的Task被封装到了NetStandard.dll,而C#则属于mscorelib.dll或.NetCoreApp/System.Runtime.dll,这取决于你使用.NetCore还是.NetFramework。

Async/Await

  • 对于所有的await部分,其返回的结果都需要实现GetAwaiter方法,这是一个TaskAwaiter类型,实现了ICriticalNotifyCompletion和INotifyCompletion接口。包含IsCompleted属性,表示该异步行为是否完成。GetResult方法获取异步的结果并且结束异步,OnCompleted异步完成的回调。
  • 编译器在遇到Async时候会将会将其包装然后生成一个实现了IAsyncStateMachine接口的类型。其中包含MoveNext等函数。可以看出这是一个表示状态机的类,MoveNext表示走到下一个状态。
  • Awaiter描述异步的结果,StateMachine控制异步的流程,还需要使用AsyncTaskMethodBuilder来对这两者进行安排和调度。SetStateMachine函数将指定与其关联的负责处理异步的状态机类,SetResult来设置异步的结果,Start开始异步(官方文档的说明是:开始运行具有关联状态的生成器)
  • 异步代码开始调用后,走MoveNext方法开始进入状态机的下一个状态(异步行为/设置结果/进入下一个状态)

其中有一个东西值得注意,那就是AsyncTaskMethodBuilder.Create方法,其中会创建一个SynchronizationContext类型的实例。这是抽象类,负责异步过程中,不同线程的通信。

Unity中的异步

接下来看看Unity官方对于Unity中的.Net异步的说明:https://docs.unity3d.com/cn/current/Manual/overview-of-dot-net-in-unity.html

【Limitations of async and await tasks】这一节中,是这样描述的。

“The Unity API isn’t thread safe and therefore, you should only use async and await tasks from inside the UnitySynchronizationContext. Async tasks often allocate objects when invoked, which might cause performance issues if you overuse them.

Unity overwrites the default SynchronizationContext with a custom UnitySynchronizationContext and runs all the tasks on the main thread in both Edit and Play modes by default. To use async tasks, you must manually create and handle your own threads with the Task.Run API, and use the default SynchronizationContext instead of the Unity version.

Unity doesn’t automatically stop async tasks that run on managed threads when you exit Play mode. To listen for enter and exit Play mode events to stop the tasks manually, use EditorApplication.playModeStateChanged. If you take this approach, most of the Unity scripting APIs aren’t available to use unless you migrate the context back to the UnitySynchronizationContext.

For performance reasons, Unity doesn’t perform checks for multithreaded behavior in non-development builds and doesn’t display this error in live builds. This means that while Unity doesn’t prevent execution of multithreaded code on live builds, random crashes and other unpredictable errors are likely if you do use multiple threads. For this reason, Unity recommends that you don’t use multithreading.

To take advantage of the benefits of multithreading safely, use the C# Job System. The Job System uses multiple threads safely to execute jobs in parallel and achieve the performance benefits of multithreading. For more information, see [What is multithreading?.

大意是这样的

UnityAPI并非线程安全的,因此你应该只使用来自 【UnitySynchronizationContext】类的async/await,异步Task通常在调用时分配对象,过度使用可能会导致性能问题

Unity将默认的【SynchronizationContext】重写为自定义的【UnitySynchronizationContext】并且在默认情况下编辑器模式和播放模式下所有的Task都运行于主线程,要使用异步任务,你必须使用Task.Run来创建和处理你自己的线程(?这里应该是指task或thread手动开启的子线程),并且使用默认的【SynchronizationContext】来代替Unity的版本(指UnitySynchronizationContext)

在开发版本中(?),如果你尝试在多线程代码中使用UnityAPI,Unity将会显示如下错误:

UnityException: Internal_CreateGameObject can only be called from the main thread. \Constructors and field initializers will be executed from the loading thread when loading a scene. \Don’t use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

出于性能原因,Unity不会在非开发版本(?)中进行多线程行为的检查,也不会在实时版本(?)中显示此错误。这意味着虽然 Unity不会阻止在实时构建中执行多线程代码,但如果你确实使用了多个线程,则可能会出现随机的crash和其他不可预测的错误。因此,Unity建议你不要使用多线程。

如果要安全的使用多线程(享受多线程的好处),请使用JobSystem。它使用多个线程安全地并行执行工作,并实现多线程的性能优势。

这里也提到了SynchronizationContext。

再看SynchronizationContext,这是一个抽象类,需要根据具体的情况做不同的实现。其中包含两个方法,Post和Send,就是这两个函数负责不同线程间的通信。通过上面的文档可以知道,Unity的异步,是继承了SynchronizationContext,然后自行实现的UnitySynchronizationContext。那么如何做到指定异步行为仍然在主线程上运行呢?就是在创建的时候显示指定了主线程ID。

但除此之外,还有别的东西会影响到是否运行在主线程上。比如ConfigureAwait。

相关文档:

关于SynchronizationContext的说明UnitySynchronizationContext源码

参考文档:

https://wudaijun.com/2021/11/c-sharp-unity-async-programing/

https://www.cnblogs.com/CrabMan/p/5436083.html

https://zhuanlan.zhihu.com/p/197335532