提问者:小点点

在Unity/C#中,。net的async/await是否启动了另一个线程?


对于任何在Unity中研究这个困难主题的人来说都是非常重要的,

请务必看到我提出的另一个问题,它提出了相关的关键问题:

具体来说,在统一中,“等待”字面上会返回到哪里?

对于C#专家,Unity是单线程的sup1/sup>

在另一个线程上进行计算是很常见的。

当你在另一个线程上做某事时,你经常使用async/wait,因为,嗯,所有优秀的C#程序员都说,这是做这件事的简单方法!

void TankExplodes() {

    ShowExplosion(); .. ordinary Unity thread
    SoundEffects(); .. ordinary Unity thread
    SendExplosionInfo(); .. it goes to another thread. let's use 'async/wait'
}

using System.Net.WebSockets;
async void SendExplosionInfo() {

    cws = new ClientWebSocket();
    try {
        await cws.ConnectAsync(u, CancellationToken.None);
        ...
        Scene.NewsFromServer("done!"); // class function to go back to main tread
    }
    catch (Exception e) { ... }
}

好的,当你这样做的时候,当你在unity/c#中以更常规的方式启动线程时(所以使用线程或者其他什么,或者让原生插件或者操作系统或者其他什么情况),你做的一切都“就像你平常做的一样”。

一切都很顺利。

作为一个蹩脚的Unity程序员,我只知道足够的C#来完成一天的工作,我一直认为上面的Async/Await模式实际上会启动另一个线程。

事实上,当您使用natty async/wait模式时,上面的代码是真的启动了另一个线程,还是ct/.net使用其他方法来完成任务?

也许它在Unity引擎中的工作方式与“普遍使用C#”不同,或者有什么特别之处?(IDK?)

注意,在Unity中,它是否是一个线程会极大地影响到您必须如何处理接下来的步骤。所以才有了问题。

问题:我知道有很多关于“等待一个线程”的讨论,但是,(1)我从未见过在Unity设置中讨论/回答这个问题(这有什么区别吗?idk?)(2)我简直从来没有见过一个明确的答案!

一些辅助计算(例如,物理计算等)是在其他线程上完成的,但实际的“基于帧的游戏引擎”是一个纯线程。(无论以何种方式“访问”主引擎框架线程都是不可能的:在编程时,例如在另一个线程上进行本机插件或某些计算时,您只需为引擎框架线程上的组件留下标记和值,以便它们在运行每个框架时查看和使用。)


共3个答案

匿名用户

这篇文章:任务(仍然)不是线程,异步不是并行的,可能会帮助您理解在引擎盖下面发生了什么。简而言之,为了使任务在单独的线程上运行,您需要调用

Task.Run(()=>{// the work to be done on a separate thread. }); 

然后你可以在任何需要的地方等待这个任务。

回答你的问题

“事实上,当您使用natty async/wait模式时,上面的代码是真的启动了另一个线程,还是CUTER/.NET使用了其他方法来完成任务?”

不-不是的。

如果你做了

await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));

将在单独的线程上运行。

作为对评论的回答,这里是修改后的代码,并作了更多的解释:

    async void SendExplosionInfo() {

        cws = new ClientWebSocket();
        try {
            var myConnectTask = Task.Run(()=>cws.ConnectAsync(u, CancellationToken.None));

            // more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task. 
            Scene.NewsFromServer("done!"); // class function to go back to main tread
        }
        catch (Exception e) { ... }
    }

但是,您可能不需要在单独的线程上使用它,因为您正在执行的异步工作不受CPU限制(或者看起来是这样)。所以你应该可以接受

 try {
            var myConnectTask =cws.ConnectAsync(u, CancellationToken.None);

            // more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task. 
            Scene.NewsFromServer("done!"); // continue from here
        }
        catch (Exception e) { ... }
    }

然后,它将在相同的线程上执行与上面代码完全相同的操作。它将允许“ConnectAsync”后面的代码执行,并且只会停下来等待“ConnectAsync”的完成,因为“ConnectAsync”不是CPU绑定的(使它在某种意义上是并行的,在其他地方正在做的工作,如网络)将有足够的果汁运行您的任务,除非您的代码在“。。。。”也需要大量的CPU绑定工作,您宁愿并行运行。

另外,您可能希望避免使用async void,因为它只用于顶级函数。尝试在方法签名中使用异步任务。你可以在这里读到更多关于这方面的内容。

匿名用户

不,Async/Await并不意味着-另一个线程。它可以启动另一个线程,但不一定要启动。

在这里您可以找到关于它的非常有趣的帖子:https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/

匿名用户

首先,你的问题的第一个陈述是有问题的。

Unity是单线程的

团结不是单线的;实际上,Unity是一个多线程环境。为什么?只需转到Unity官方网页并在那里阅读:

高性能多线程系统:充分利用今天(和明天)可用的多核处理器,无需繁重的编程。我们支持高性能的新基础由三个子系统组成:C#作业系统,它为编写并行代码提供了一个安全而容易的沙箱;实体组件系统(ECS),默认情况下用于编写高性能代码的模型,以及突发编译器,它生成高度优化的本机代码。

Unity 3D引擎使用一个名为“Mono”的。NET运行时,该运行时本质上是多线程的。对于某些平台,托管代码将转换为本机代码,因此不会有。NET运行时。但是代码本身无论如何都是多线程的。

所以请不要陈述误导性的和技术上不正确的事实。

您所争论的,仅仅是一个声明,即Unity中有一个主线程,它以基于框架的方式处理核心工作负载。这是真的。但这不是什么新的和独特的东西!例如。在。NET Framework(或从3.0开始的。NET Core)上运行的WPF应用程序也有一个主线程(通常称为UI线程),并且工作负载是使用WPF(分派器队列,操作,帧等)以基于框架的方式在该线程上处理的,但所有这些并没有使环境成为单线程的!这只是处理应用程序逻辑的一种方法。

请注意:我的答案只适用于运行。NET运行时环境(Mono)的这类Unity实例。对于那些将托管C#代码转换为本机C++代码并构建/运行本机二进制文件的实例,我的答案最有可能至少是不准确的。

你写:

当你在另一个线程上做某事时,你经常使用async/wait,因为,嗯,所有优秀的C#程序员都说,这是做这件事的简单方法!

C#中的关键字只是使用TAP(任务异步模式)的一种方式。

抽头用于任意异步操作。一般来说,没有螺纹。我强烈推荐阅读这篇Stephen Cleary的文章,名为“There is no threaded”。(Stephen Cleary是一位著名的异步编程大师,如果您不知道的话。)

使用特性的主要原因是异步操作。您使用并不是因为“您在另一个线程上做某事”,而是因为您有一个异步操作需要等待。无论是否有后台线程运行此操作-这对您来说都无关紧要(好吧,几乎见下文)。TAP是隐藏这些细节的抽象级别。

事实上,当您使用natty async/wait模式时,上面的代码是真的启动了另一个线程,还是ct/.net使用其他方法来完成任务?

正确答案是:视情况而定。

    立即引发参数验证异常(例如,当为null时发生),则不会启动任何新线程/LI>
  • 如果方法的实现是一个纯异步操作,不涉及线程,那么您的调用方法将被“挂起”(由于)-因此不会启动新线程/li> 能够在正在运行的线程池线程上调度此工作项,则不会启动任何新线程;相反,工作项将在一个已经运行的线程池thread/li>上排队

你看,这相当复杂。但这正是在C#中引入TAP模式和关键字对的原因。这些通常是开发人员不想费心去做的事情,所以让我们把这些东西隐藏在运行时/框架中。

@agfc声明了一件不太正确的事情:

“这不会在后台线程上运行该方法”

await cws.ConnectAsync(u, CancellationToken.None);

“但这将”

await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));

如果

请注意,有一个异步后缀,并返回一个。这是一个基于常规的信息,即该方法是真正异步的。在这种情况下,您应该始终首选而不是MethodAsync())/code>。

更多有趣的阅读:

    vs vs