为了账号安全,请及时绑定邮箱和手机立即绑定

使用来自其他线程的统一API或调用主线程中的函数

/ 猿问

使用来自其他线程的统一API或调用主线程中的函数

泛舟湖上清波郎朗 2019-06-10 16:43:01

使用来自其他线程的统一API或调用主线程中的函数

我的问题是,我试图使用UnitySocket来实现一些东西。每次,当我收到一条新消息时,我需要将它更新为更新文本(它是一个统一文本)。但是,当我执行以下代码时,无效更新并不是每次都调用。

我不包括updatetext.GetComponent<Text>().text = "From server: "+tempMesg;在voidgetInformation中,这个函数在线程中,当我在getInformation()中包含这个函数时,它会出现一个错误:

getcomponentfastpath can only be called from the main thread

我想问题是我不知道如何在C#中一起运行主线程和子线程?或许还有其他问题.。希望有人能帮忙.。这是我的密码:

using UnityEngine;using System.Collections;using System;using System.Net.Sockets;using System.Text;using System.Threading;using UnityEngine.
UI;public class Client : MonoBehaviour {

    System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
    private Thread oThread;//  for UI update
    public GameObject updatetext;
    String tempMesg = "Waiting...";

    // Use this for initialization
    void Start () {
        updatetext.GetComponent<Text>().text = "Waiting...";
        clientSocket.Connect("10.132.198.29", 8888);
        oThread = new Thread (new ThreadStart (getInformation));
        oThread.Start ();
        Debug.Log ("Running the client");
    }

    // Update is called once per frame
    void Update () {
        updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
        Debug.Log (tempMesg);
    }

    void getInformation(){
        while (true) {
            try {
                NetworkStream networkStream = clientSocket.GetStream ();
                byte[] bytesFrom = new byte[10025];
                networkStream.Read (bytesFrom, 0, (int)bytesFrom.Length);
                string dataFromClient = System.Text.Encoding.ASCII.GetString (bytesFrom);
                dataFromClient = dataFromClient.Substring (0, dataFromClient.IndexOf ("$"));
                Debug.Log (" >> Data from Server - " + dataFromClient);

                tempMesg = dataFromClient;

                string serverResponse = "Last Message from Server" + dataFromClient;


查看完整描述

3 回答

?
若吾皇

团结不是Thread安全,所以他们决定不可能从另一个调用他们的api。Thread通过添加一种机制,在从另一个地方使用其api时抛出异常。Thread.

这个问题已经问了很多次了,但没有适当的解决办法/答案。答案通常是“使用插件”或做一些非线程安全的事情。希望这将是最后一次。

您通常会在Stackoverover或United的论坛网站上看到的解决方案是,只需使用boolean变量使主线程知道您需要在主线程中执行代码。Thread..这是不对的,因为它是不对的。螺纹安全并且不给您提供调用哪个函数的控制权。如果你有多个Threads需要通知主线程吗?

您将看到的另一个解决方案是使用协同线而不是Thread..这确实是工作。对套接字使用协同线不会改变任何事情。你最终还是会冻结问题。你必须坚持你的Thread代码或使用Async.

这样做的正确方法之一是创建一个集合,如List..当您需要在主线程中执行某些内容时,请调用一个将代码存储在Action..收到ListAction到当地ListAction然后从本地执行代码。Action在那List那就弄清楚List..这防止了其他人Threads等它完成执行。

您还需要添加一个volatile boolean通知Update函数中有代码在等待。List被处决。复制List到当地List,它应该被包裹在lock关键字,以防止另一个线程写入它。

执行我前面提到的内容的脚本:

UnityThread剧本:

#define ENABLE_UPDATE_FUNCTION_CALLBACK#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK

using System;using System.Collections;using UnityEngine;using System.Collections.Generic;public class UnityThread : MonoBehaviour{
    //our (singleton) instance
    private static UnityThread instance = null;


    ////////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////////
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueUpdateFunc then executed from there
    private static List<System.Action> actionQueuesUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesUpdateFunc to be executed
    List<System.Action> actionCopiedQueueUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteUpdateFunc = true;


    ////////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////////
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueLateUpdateFunc then executed from there
    private static List<System.Action> actionQueuesLateUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesLateUpdateFunc to be executed
    List<System.Action> actionCopiedQueueLateUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteLateUpdateFunc = true;



    ////////////////////////////////////////////////FIXEDUPDATE IMPL////////////////////////////////////////////////////////
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueFixedUpdateFunc then executed from there
    private static List<System.Action> actionQueuesFixedUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesFixedUpdateFunc to be executed
    List<System.Action> actionCopiedQueueFixedUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteFixedUpdateFunc = true;


    //Used to initialize UnityThread. Call once before any function here
    public static void initUnityThread(bool visible = false)
    {
        if (instance != null)
        {
            return;
        }

        if (Application.isPlaying)
        {
            // add an invisible game object to the scene
            GameObject obj = new GameObject("MainThreadExecuter");
            if (!visible)
            {
                obj.hideFlags = HideFlags.HideAndDontSave;
            }

            DontDestroyOnLoad(obj);
            instance = obj.AddComponent<UnityThread>();
        }
    }

    public void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }

    //////////////////////////////////////////////COROUTINE IMPL//////////////////////////////////////////////////////
    #if (ENABLE_UPDATE_FUNCTION_CALLBACK)
    public static void executeCoroutine(IEnumerator action)
    {
        if (instance != null)
        {
            executeInUpdate(() => instance.StartCoroutine(action));
        }
    }

    ////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////
    public static void executeInUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesUpdateFunc)
        {
            actionQueuesUpdateFunc.Add(action);
            noActionQueueToExecuteUpdateFunc = false;
        }
    }

    public void Update()
    {
        if (noActionQueueToExecuteUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueUpdateFunc queue
        actionCopiedQueueUpdateFunc.Clear();
        lock (actionQueuesUpdateFunc)
        {
            //Copy actionQueuesUpdateFunc to the actionCopiedQueueUpdateFunc variable
            actionCopiedQueueUpdateFunc.AddRange(actionQueuesUpdateFunc);
            //Now clear the actionQueuesUpdateFunc since we've done copying it
            actionQueuesUpdateFunc.Clear();
            noActionQueueToExecuteUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueUpdateFunc
        for (int i = 0; i < actionCopiedQueueUpdateFunc.Count; i++)
        {
            actionCopiedQueueUpdateFunc[i].Invoke();
        }
    }#endif

    ////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////
    #if (ENABLE_LATEUPDATE_FUNCTION_CALLBACK)
    public static void executeInLateUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesLateUpdateFunc)
        {
            actionQueuesLateUpdateFunc.Add(action);
            noActionQueueToExecuteLateUpdateFunc = false;
        }
    }


    public void LateUpdate()
    {
        if (noActionQueueToExecuteLateUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueLateUpdateFunc queue
        actionCopiedQueueLateUpdateFunc.Clear();
        lock (actionQueuesLateUpdateFunc)
        {
            //Copy actionQueuesLateUpdateFunc to the actionCopiedQueueLateUpdateFunc variable
            actionCopiedQueueLateUpdateFunc.AddRange(actionQueuesLateUpdateFunc);
            //Now clear the actionQueuesLateUpdateFunc since we've done copying it
            actionQueuesLateUpdateFunc.Clear();
            noActionQueueToExecuteLateUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueLateUpdateFunc
        for (int i = 0; i < actionCopiedQueueLateUpdateFunc.Count; i++)
        {
            actionCopiedQueueLateUpdateFunc[i].Invoke();
        }
    }#endif

    ////////////////////////////////////////////FIXEDUPDATE IMPL//////////////////////////////////////////////////
    #if (ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK)
    public static void executeInFixedUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesFixedUpdateFunc)
        {
            actionQueuesFixedUpdateFunc.Add(action);
            noActionQueueToExecuteFixedUpdateFunc = false;
        }
    }

    public void FixedUpdate()
    {
        if (noActionQueueToExecuteFixedUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueFixedUpdateFunc queue
        actionCopiedQueueFixedUpdateFunc.Clear();
        lock (actionQueuesFixedUpdateFunc)
        {
            //Copy actionQueuesFixedUpdateFunc to the actionCopiedQueueFixedUpdateFunc variable
            actionCopiedQueueFixedUpdateFunc.AddRange(actionQueuesFixedUpdateFunc);
            //Now clear the actionQueuesFixedUpdateFunc since we've done copying it
            actionQueuesFixedUpdateFunc.Clear();
            noActionQueueToExecuteFixedUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueFixedUpdateFunc
        for (int i = 0; i < actionCopiedQueueFixedUpdateFunc.Count; i++)
        {
            actionCopiedQueueFixedUpdateFunc[i].Invoke();
        }
    }#endif

    public void OnDisable()
    {
        if (instance == this)
        {
            instance = null;
        }
    }}

使用:

此实现允许您调用3最常用的统一功能:UpdateLateUpdateFixedUpdate职能。这还允许您在主程序中调用运行cooutine函数。Thread..它可以扩展为能够在其他统一回调函数中调用函数,例如OnPreRenderOnPostRender.

1.首先,从Awake()功能。

void Awake(){
    UnityThread.initUnityThread();}

2.在主目录中执行代码Thread来自另一个线程:

UnityThread.executeInUpdate(() =>{
    transform.Rotate(new Vector3(0f, 90f, 0f));});

这将旋转当前对象的枕木连接到90度。现在可以使用UnityAPI(transform.Rotate)在另一个Thread.

3.调用主函数Thread来自另一个线程:

Action rot = Rotate;UnityThread.executeInUpdate(rot);void Rotate(){
    transform.Rotate(new Vector3(0f, 90f, 0f));}

这个#2#3示例在Update功能。

4.若要在LateUpdate函数来自另一个线程:

这是一个相机跟踪代码的例子。

UnityThread.executeInLateUpdate(()=>{
    //Your code camera moving code});

5.若要在FixedUpdate函数来自另一个线程:

例如,在做物理操作时,例如向Rigidbody.

UnityThread.executeInFixedUpdate(()=>{
    //Your code physics code});

6.在主目录中启动cooutine函数Thread来自另一个线程:

UnityThread.executeCoroutine(myCoroutine());IEnumerator myCoroutine(){
    Debug.Log("Hello");
    yield return new WaitForSeconds(2f);
    Debug.Log("Test");}

最后,如果不需要在LateUpdateFixedUpdate函数,您应该在下面的代码中对这两行代码进行注释:

//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK

这将提高性能。


查看完整回答
反对 回复 2019-06-10
?
慕田峪4524236

许多关于统一中的线程的文章实际上都是完全不正确的。

怎么会这样?

基本事实是,统一当然是完全以框架为基础的。

当您在基于框架的系统中工作时,线程问题与传统系统中的线程问题有很大的不同。

这是一种完全不同的模式。

(实际上,在许多方面,这要容易得多。)

基于框架的系统上的线程问题与传统系统完全不同.(在某些方面,更容易因为某些概念根本不存在于基于框架的系统中。)

假设您有一个显示值的统一温度计显示器

Thermo.cs

enter image description here

因此它将有一个在Update中调用的函数,如

func void ShowThermoValue(float fraction) {
   display code, animates a thermometer}

每帧只运行一次,仅此而已。

很容易忘记这一基本事实.

当然,它只运行在“主线程”上。

(团结没有别的东西了!只是.“统一线”!)

但要记住的关键范例是:它只运行一次帧.

现在,在热.cs中的其他地方,您将拥有一个处理“新值已经到来”概念的函数:

[MonoPInvokeCallback(typeof(ipDel))]public static void NewValueArrives(float f) {

    ... ???}

注意,当然,这是一个类函数!

你不能以任何方式“到达”一个正常的统一功能,如显示热值!脚注1

这是一个完全、毫无意义的概念。你不能“伸手进去”炫耀热值。

您所看到的任何与线程相关的代码示例(用于Unitity),试图“触及”一个线程是完全误导.

没有线可触及!

同样-这是令人惊讶的-如果你只是在WWW上搜索所有关于团结中的线程的文章,那么许多文章和帖子在概念层面上都是完全不正确的。

比方说:价值观来得非常频繁,而且不规律。

您可以将各种科学设备连接到个人电脑和iphone的机架上,以及新的“温度”值。会经常到达.。每帧数十次..或者每隔几秒钟。

那么你是怎么处理这些值的呢?

再简单不过了。

从到达值线程,你所做的就是.等待.在组件中设置一个变量!

WTF?你所做的就是设置变量?就这样?怎么会这么简单?

这是其中一种不寻常的情况:

  1. 在“统一”中,很多关于线程的写作只是大错特错..这并不是说它有“一个错误”或“一些需要纠正的东西”。这是“完全和完全错误的”:在基本的范式层面。

  2. 令人惊讶的是,实际的方法非常简单。

  3. 它是如此简单,你可能会认为你做错了什么。

所以有变量.。

[System.Nonserialized] public float latestValue;

把它放在“到达线”上.。

[MonoPInvokeCallback(typeof(ipDel))]public static void NewValueArrives(float f) {

    ThisScript.runningInstance.latestValue = f; // done}

老实说就是这样。

本质上,成为世界上最伟大的“统一中的线程”专家-这显然是基于框架的-除了以上这些没有什么可做的了。

无论何时ShowThermoValue称为每帧.只需显示这个值!

真的,就是这样!

[System.Nonserialized] public float latestValue;func void ShowThermoValue() { // note NO arguments here!
   display code, animates a thermometer
   thermo height = latestValue}

您只需显示“最新”值。

最近的价值可能被设定了一次,两次,十次,或者是这个框架的一百倍.但是,您只需在ShowThermoValue运行那个框架!

你还能展示什么?

温度计正在屏幕上以60英尺的速度更新。脚注2

其实有那么简单。就是这么简单。令人惊讶但却是真的。


(批评的旁白-别忘了向量3,等等,在统一/C#中不是原子的)

正如user@dymanid所指出的(阅读下面的重要讨论),重要的是要记住,虽然Float是原子的,但是其他任何东西(例如,Vector 3等)都不是原子的。通常(如这里的例子),您只从本地插件的计算中传递浮点数。但重要的是要意识到向量等等并不是原子的。


有时,有经验的线程程序员会与基于帧的系统打结,因为:在基于帧的系统中,大多数由跑道和锁定问题引起的问题.在概念上不存在。

在基于框架的系统中,任何游戏项目都应该是基于某个“当前值”(在某个地方设置的)来显示或表现的。如果你有来自其他线程的信息,只要设置这些值 - 已完成.

你,你们不能有意义 “与主线对话”因为那条主线.是基于帧的!

搁置线程问题,一个连续的系统不能有意义地交谈框架范式系统。

大多数锁定、阻塞和跑道问题是不存在在基于框架的范式中,因为:如果你在一个特定的框架中被设置了十次,一百万次,十亿次。你能做什么?您只能显示一个值!

想想老式的塑料薄膜。你真的有.一个框架,仅此而已。如果在一个特定的帧中设置了1万亿次延迟值,Show热值将只显示它在运行时获取的一个值(在这60秒内)。

你所做的就是:把信息放在某个地方,如果它愿意的话,框架-范式系统将使用这个框架。

简单地说就是这样。

因此,大多数“线程问题”消失在团结。

所有你能做的从…

  • 其他计算线程或

  • 从插件线程,

只是游戏可能使用的“下降值”。

就这样!


脚注


1你怎么能?作为一个思想实验,忘记你处在另一条线上的问题吧。显示热值由框架引擎运行一次。你不能用任何有意义的方式“称呼”它。与普通的OO软件不同,您不能实例化类的实例(组件?)并运行该函数-这是完全没有意义的。

在“正常”线程编程中,线程可以回话和回话等等,在这样做时,您需要考虑锁定、跑道等问题。但仅此而已无意义在基于帧的ECS系统中。没有什么可以“交谈”的。

假设团结实际上是多线程的!团结组织的人让所有的引擎都以多线程的方式运行。不会有任何区别-你不能以任何有意义的方式“进入”展示热力价值!它是框架引擎的一个组件运行一次帧仅此而已。

所以NewValueArrives不是任何地方-它是一个类函数!

让我们回答标题中的问题:

“使用来自另一个线程的UnityAPI,还是调用主线程中的函数?”

概念是>完全没有意义 <<. Unity (like all game engines) is frame-based. There's no concept of "calling" a function on the main thread. To make an analogy: it would be like a cinematographer in the celluloid-film era asking how to "move" something actually 在……上面其中一个框架。

enter image description here

当然,这是毫无意义的。你所能做的就是为下一张照片,下一帧改变一些东西。


2我指的是“到达价值线”.事实上!NewValueArrives可以在主线程上运行,也可以不运行!它可以在插件的线程上运行,或者在其他线程上运行!实际上,在您处理NewValueArrives调用时,它可能是完全单线程的!只是没关系!在一个基于框架的范例中,你所能做的就是“躺在周围”,诸如此类的组件可以使用它们认为合适的信息。


查看完整回答
反对 回复 2019-06-10
?
慕慕森

我一直在用这个解决方案来解决这个问题。使用此代码创建脚本,并将其附加到“游戏”对象:

using System;using System.Collections.Generic;using System.Collections.Concurrent;using UnityEngine;public class ExecuteOnMainThread :
 MonoBehaviour {

    public readonly static ConcurrentQueue<Action> RunOnMainThread = new ConcurrentQueue<Action>();

    void Update()
    {
        if(!RunOnMainThread.IsEmpty())
        {
           while(RunOnMainThread.TryDequeue(out action))
           {
             action.Invoke();
           }
        }
    }}

然后,当您需要调用主线程上的某个内容并从应用程序中的任何其他函数访问UnityAPI时:

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {

    // Code here will be called in the main thread...});


查看完整回答
反对 回复 2019-06-10

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信