具体来说,在Unity中,等待在哪里真正地返回到“哪里”?

Fat*_*tie 4 .net c# multithreading unity-game-engine

在Unity中,说您有一个GameObject。因此,可能是劳拉·克罗夫特(Lara Croft),马里奥(Mario),愤怒的鸟,特定的立方体,特定的树或其他任何东西。

在此处输入图片说明

(回想一下,Unity不是OO,它是ECS。Component可以“附加”到s的s本身可以GameObject用OO语言创建,也可以不使用OO语言创建,但是Unity本身只是GameObjects 的列表和运行任何Components 的框架引擎。因此,实际上Unity当然是“完全”的单线程,甚至没有一种概念上的方法可以在另外1个线程上执行与“实际Unity”(“游戏对象列表”)相关的任何操作。)

所以说在多维数据集上,我们有一个Component称为Test

public class Test: MonoBehaviour {
Run Code Online (Sandbox Code Playgroud)

它确实具有Update伪函数,因此Unity知道我们要在每个帧中运行一些东西。

  private void Update() { // this is Test's Update call

     Debug.Log(ManagedThreadId); // definitely 101
     if (something) DoSomethingThisParticularFrame();
  }
Run Code Online (Sandbox Code Playgroud)

假设统一线程为“ 101”。

这样更新(实际上是任何游戏对象上任何框架的任何更新)都将打印101。

因此,由于某种原因,我们有时会选择每隔几秒钟运行一次DoSomethingThisFrame

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

因此,在一个特定的帧(比如说游戏的第819秒的第24帧)上,它确实DoSomethingThisParticularFrame为我们运行。

void DoSomethingThisParticularFrame() {

   Debug.Log(ManagedThreadId); // 101 I think
   TrickyBusiness();
}
Run Code Online (Sandbox Code Playgroud)

我认为这也会打印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
}
Run Code Online (Sandbox Code Playgroud)

好吧

  1. 我很确定在A上它将打印101。我认为。

  2. 我想在B它将打印101

  3. 我相信,但是我不确定,在X它将启动另一个BigCalculation线程。(说999。)(但谁知道那可能是错误的。)

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

我们在C处使用什么线程,它(试图?)在哪里炸破坦克????

我相信在普通的.Net环境中,您可能会在C的另一个线程上工作,例如202。

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

但这在Unity中毫无意义-

...怎么可能TrickyBusiness在“另一个线程”上-整个场景是重复的,还是什么意思?

还是这种情况(尤其是在Unity中并且仅在IDK中),

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

在Unity中,当您使用此await内容时,它在C或A上打印什么?

似乎:

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


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

GSe*_*erg 8

异步作为高级抽象与线程无关

await 所控制后,在哪个线程上恢复执行System.Threading.SynchronizationContext.Current

例如,WindowsFormsSynchronizationContext将确保从GUI线程开始的执行将在结束后在GUI线程上继续await执行,因此,如果您在WinForms应用程序中执行测试,ManagedThreadId则在之后将看到相同的结果await

例如,不关心保留线程,将允许代码在任何线程上恢复。AspNetSynchronizationContext

例如,ASP.NET Core 根本没有同步上下文

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


上面是事件的“足够真实”表示,也就是说,您可以从正常无聊的日常async / await代码中得到期望,这些代码与常规Task<T>函数有关,这些常规函数以通常的方式返回结果。

您绝对可以调整以下行为:

  • 您可以通过ConfigureAwait(false)等待调用来放弃上下文捕获。由于未捕获上下文,因此上下文附带的所有内容都会丢失,包括在原始线程上恢复的能力(对于与线程有关的上下文)。

  • 您可以设计一个异步代码,即使您不使用异步代码,也可以在线程之间有意识地进行切换ConfigureAwait(false)。一个很好的例子可以在Raymond Chen的博客(第1部分第2部分)中找到,并展示了如何在使用

    await ThreadSwitcher.ResumeBackgroundAsync();
    
    Run Code Online (Sandbox Code Playgroud)

    然后回来

    await ThreadSwitcher.ResumeForegroundAsync(Dispatcher);
    
    Run Code Online (Sandbox Code Playgroud)
  • 因为整个异步/等待机制是松散耦合的(您可以使用await 任何定义GetAwaiter()方法的对象),所以您可以提出一个对象,该对象GetAwaiter()在当前线程/上下文中可以做您想做的所有事情(实际上,这正是上面的项目符号项)是)。

SynchronizationContext.Current不会神奇地在其他人的代码上强制执行其方法:反之亦然。SynchronizationContext.Current之所以生效,是因为Task<T> 选择执行尊重它。您可以自由地实现一个忽略它的等待方式。

  • 至于恢复执行,我会说它不是100%准确的,因为`ConfigureAwait`也是对此有影响的因素。除此之外,`async / await`只是一个抽象,您可以构建自己的awaiter / awaitable,它将始终在原始线程上继续运行,或者完全忽略同步上下文。 (2认同)
  • 好吧,如果*你*通过调用`ConfigureAwait(false)`说“请忽略上下文”,当前上下文将被忽略,这并不奇怪。但如果*调用的代码*这样做了,*您*将[仍然在您的上下文中恢复](/sf/answers/1949602231/)。 (2认同)
  • 供参考:https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/UnitySynchronizationContext.cs 似乎本机 Unity 运行时在 Unity 主线程内调用 `ExecuteTasks()`,但因此到目前为止我还没有发现它是在“Update()”框架部分还是在其他点。例如与物理学相关。 (2认同)