c# - 具体来说,在 Unity 中, "where"是否真的等待返回?

标签 c# .net multithreading unity3d

在 Unity 中,假设您有一个 GameObject 。因此,它可能是 Lara Croft、Mario、一只愤怒的小鸟、一个特定的立方体、一棵特定的树,或者其他任何东西。

enter image description here

(回想一下,Unity 不是 OO,它是 ECS。您可以“附加”到 GameObjectComponent 本身可能会也可能不会在 OO 中创建语言,但 Unity 本身只是 GameObject 的列表和一个框架引擎,每帧在其上运行任何 Component。因此,Unity 当然是“完全”单一的-线程,甚至没有一种概念性的方法可以在另一个1 线程上执行与“实际 Unity”(“游戏对象列表”)相关的任何事情。)

假设在立方体上我们有一个名为 Test 的 Component

public class Test: MonoBehaviour {

它确实有一个 Update 伪函数,所以 Unity 知道我们想要在每一帧运行一些东西。

  private void Update() { // this is Test's Update call
     
     Debug.Log(ManagedThreadId); // definitely 101
     if (something) DoSomethingThisParticularFrame();
  }

假设统一线程是“101”。

因此更新(实际上是任何游戏对象上任何帧的任何更新)将打印 101。

所以有时,也许出于某种原因每隔几秒,我们选择运行 DoSomethingThisFrame

因此,每一帧(显然,在“那个”Unity 线程上……有/只能有一个线程)Unity 对各种游戏对象运行所有更新调用。

所以在一个特定的帧上(假设是游戏第 819 秒的第 24 帧),假设它确实为我们运行了 DoSomethingThisParticularFrame

void DoSomethingThisParticularFrame() {

   Debug.Log(ManagedThreadId); // 101 I think
   TrickyBusiness();
}

我假设这也会打印 101。

async void TrickyBusiness() {

   Debug.Log("A.. " + ManagedThreadId); // 101 I think
   var aTask = Task.Run(()=>BigCalculation());
   
   Debug.Log("B.. " + ManagedThreadId); // 101 I think
   await aTask;

   Debug.Log("C.. " + ManagedThreadId); // In Unity a mystery??
   ExplodeTank();
}

void BigCalculation() {
   
   Debug.Log("X.. " + ManagedThreadId); // say, 999
   for (i = 1 to a billion) add
}

好吧

  1. 我很确定在 A 处它会打印 101。我想。

  2. 我猜在 B 处它会打印 101

  3. 我相信,但我不确定,在 X 上它会为 BigCalculation 启动另一个线程。 (比如说,999。)(但也许这是错误的,谁知道呢。)

我的问题是,在 Unity 中的“C”处会发生什么?

我们在 C 的哪个线程上,它(试图?)引爆坦克????

我相信在正常的 .Net 环境中,您会在 C 的另一个线程上,比如 202。

(例如,考虑这个 excellent answer 并注意第一个示例输出“Thread After Await: 12”。12 与 29 不同。)

但这在 Unity 中毫无意义 -

...TrickyBusiness 怎么会在“另一个线程”上 - 那是什么意思,整个场景 是重复的,或者?

或者是这种情况(尤其是在 Unity 中?IDK),

TrickyBusiness 开始的地方,Unity 实际上将它(什么 - 类“Test”的裸实例??)放在另一个线程上?

在 Unity 中,当您使用 await 时,它会在 C 或 A 打印什么?

这似乎是:

如果“C”确实在不同的线程上——您根本不能在 Unity 中以这种方式使用等待,那将毫无意义。


1 显然一些辅助计算(例如,渲染等)是在其他内核上完成的,但实际的“基于帧的游戏引擎”是一个纯线程。 (不可能以任何方式“访问”主引擎框架线程:当您编程时,比如说,本地插件或在另一个线程上运行的某些计算,您所能做的就是在组件上留下标记和值引擎框架线程在运行每个框架时查看和使用。)

最佳答案

作为高级抽象的异步是 not concerned with threads .

await 后在哪个线程上恢复执行is controlled通过 System.Threading.SynchronizationContext.Current .

例如 WindowsFormsSynchronizationContext 将确保在 GUI 线程上启动的执行将在 await 之后在 GUI 线程上恢复。 ,因此如果您在 WinForms 应用程序中执行测试,您将看到 ManagedThreadIdawait之后是一样的.

例如 AspNetSynchronizationContext does not关心保留线程并允许代码在任何线程上恢复。

例如ASP.NET 核心 does not have a synchronization context at all .

Unity 会发生什么取决于它的 SynchronizationContext.Current .您可以检查它返回的内容。


以上是事件的“足够真实”的表示,也就是说,您可以从与常规 Task<T> 相关的普通无聊的日常异步/等待代码中得到什么。以通常方式返回结果的函数。

您绝对可以调整这些行为:

  • 您可以放弃上下文捕获 by calling ConfigureAwait(false) 与你的等待。由于未捕获上下文,上下文附带的所有内容都将丢失,包括在原始线程上恢复的能力(对于与线程相关的上下文)。

  • 即使您没有使用 ConfigureAwait(false),您也可以设计有意在线程之间切换的异步代码。 .一个很好的例子可以在 Raymond Chen 的博客(part 1part 2)中找到,它展示了如何在一个方法的中间显式地跳转到另一个线程

    await ThreadSwitcher.ResumeBackgroundAsync();
    

    然后回来

    await ThreadSwitcher.ResumeForegroundAsync(Dispatcher);
    
  • 因为整个 async/await 机制是松散耦合的(你可以 await any object that defines a GetAwaiter() method ),你可以想出一个对象,其 GetAwaiter()对当前线程/上下文做任何你想做的事(事实上,这正是上面的项目符号项)。

SynchronizationContext.Current不会神奇地将其方式强加于其他人的代码:恰恰相反。 SynchronizationContext.Current仅在执行 Task<T> 时有效chooses to respect it .您可以自由实现忽略它的不同等待。

关于c# - 具体来说,在 Unity 中, "where"是否真的等待返回?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55613081/

相关文章:

c# - 在 C# 中以函数式方式处理数据库事务

c# - 在运行时创建具有通用接口(interface)的通用类型

C# this.Enqueue() 使用 "temp"对象数据入队时的奇怪行为

c# - ExecuteNonQuery 不返回值?

c++ - 解锁对 STL vector::size 安全性的访问

multithreading - 使用 R 中的 Snow 包生成随机数

c# - 使用 Dapper 将参数传递到 SQL 校验和函数

c# - 在标准 C# .NET 项目中使用 Script# 代码进行字符串操作

c# - 如何从 C# 控制台应用程序发送通知

Java 线程无法与链表正常工作