从另一个线程使用 Unity API 或调用主线程中的函数 [英] Use Unity API from another Thread or call a function in the main Thread

查看:106
本文介绍了从另一个线程使用 Unity API 或调用主线程中的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题是我尝试使用 Unity 套接字来实现某些东西.每次,当我收到一条新消息时,我都需要将其更新为更新文本(它是一个 Unity 文本).但是,当我执行以下代码时,每次都不会调用 void 更新.

My problem is I try to use Unity socket to implement something. Each time, when I get a new message I need to update it to the updattext (it is a Unity Text). However, When I do the following code, the void update does not calling every time.

我不包括updatetext.GetComponent().text = "From server: "+tempMesg; in the void getInformation 的原因是这个函数在线程中,当我在 getInformation() 中包含它会出现错误:

The reason for I do not include updatetext.GetComponent<Text>().text = "From server: "+tempMesg;in the void getInformation is this function is in the thread, when I include that in getInformation() it will come with an error:

getcomponentfastpath 只能从主线程调用

我想问题是我不知道如何在C#中同时运行主线程和子线程?或者可能还有其他问题.

I think the problem is I don't know how to run the main thread and the child thread in C# together? Or there maybe other problems.

这是我的代码:

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;

                Byte[] sendBytes = Encoding.ASCII.GetBytes (serverResponse);
                networkStream.Write (sendBytes, 0, sendBytes.Length);
                networkStream.Flush ();
                Debug.Log (" >> " + serverResponse);

            } catch (Exception ex) {
                Debug.Log ("Exception error:" + ex.ToString ());
                oThread.Abort ();
                oThread.Join ();
            }
//          Thread.Sleep (500);
        }
    }
}

推荐答案

Unity 不是 Thread 安全的,所以他们决定不能从另一个 Thread 调用他们的 API> 添加一种机制,当它的 API 被另一个 Thread 使用时抛出异常.

Unity is not Thread safe, so they decided to make it impossible to call their API from another Thread by adding a mechanism to throw an exception when its API is used from another Thread.

这个问题已经被问过很多次了,但没有一个正确的解决方案/答案.答案通常是使用插件"或做一些非线程安全的事情.希望这是最后一次.

This question has been asked so many times, but there have been no proper solution/answer to any of them. The answers are usually "use a plugin" or do something not thread-safe. Hopefully, this will be the last one.

您通常在 Stackoverflow 或 Unity 论坛网站上看到的解决方案是简单地使用 boolean 变量让主线程知道您需要在主 Thread.这是不对的,因为它不是线程安全,并且不能让您控制提供要调用的函数.如果你有多个Threads需要通知主线程怎么办?

The solution you will usually see on Stackoverflow or Unity's forum website is to simply use a boolean variable to let the main thread know that you need to execute code in the main Thread. This is not right as it is not thread-safe and does not give you control to provide which function to call. What if you have multiple Threads that need to notify the main thread?

您将看到的另一个解决方案是使用协程而不是 Thread.这不起作用.对套接字使用协程不会改变任何东西.您最终仍会遇到冻结问题.您必须坚持使用 Thread 代码或使用 Async.

Another solution you will see is to use a coroutine instead of a Thread. This does not work. Using coroutine for sockets will not change anything. You will still end up with your freezing problems. You must stick with your Thread code or use Async.

执行此操作的正确方法之一是创建一个集合,例如 List.当您需要在主线程中执行某些操作时,请调用一个函数,该函数将要执行的代码存储在 Action 中.将 ActionList 复制到 Action 的本地 List 然后从本地 Action 执行代码 在那个 List 然后清除那个 List.这可以防止其他 Threads 不得不等待它完成执行.

One of the proper ways to do this is to create a collection such as List. When you need something to be executed in the main Thread, call a function that stores the code to execute in an Action. Copy that List of Action to a local List of Action then execute the code from the local Action in that List then clear that List. This prevents other Threads from having to wait for it to finish executing.

您还需要添加一个volatile boolean 来通知Update 函数在List 中有等待执行的代码.将 List 复制到本地 List 时,应将其包裹在 lock 关键字周围以防止另一个线程写入.

You also need to add a volatile boolean to notify the Update function that there is code waiting in the List to be executed. When copying the List to a local List, that should be wrapped around the lock keyword to prevent another Thread from writing to it.

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

A script that performs what I mentioned above:

UnityThread 脚本:

UnityThread Script:

#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 最常用的 Unity 函数中的函数:UpdateLateUpdateFixedUpdate 功能.这也允许您在主 Thread 中调用运行协程函数.它可以扩展为能够调用其他Unity回调函数中的函数,例如OnPreRenderOnPostRender.

This implementation allows you to call functions in the 3 most used Unity functions: Update, LateUpdate and FixedUpdate functions. This also allows you call run a coroutine function in the main Thread. It can be extended to be able to call functions in other Unity callback functions such as OnPreRender and OnPostRender.

1.首先,从 Awake() 函数初始化它.

1.First, initialize it from the Awake() function.

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

2.从另一个线程执行主Thread中的代码:

2.To execute a code in the main Thread from another Thread:

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

这会将 scipt 附加到的当前对象旋转 90 度.您现在可以在另一个 Thread 中使用 Unity API(transform.Rotate).

This will rotate the current Object the scipt is attached to, to 90 deg. You can now use Unity API(transform.Rotate) in another Thread.

3.从另一个线程调用主Thread中的函数:

3.To call a function in the main Thread from another Thread:

Action rot = Rotate;
UnityThread.executeInUpdate(rot);


void Rotate()
{
    transform.Rotate(new Vector3(0f, 90f, 0f));
}

#2#3 示例在 更新函数.

The #2 and #3 samples executes in the Update function.

4.在中执行代码另一个线程的 LateUpdate 函数:

此示例是相机跟踪代码.

Example of this is a camera tracking code.

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

5.在中执行代码来自另一个线程的 FixedUpdate 函数:

在进行物理操作时的示例,例如向 Rigidbody 添加力.

Example of this when doing physics stuff such as adding force to Rigidbody.

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

6.从另一个线程启动主Thread中的协程函数:

6.To Start a coroutine function in the main Thread from another Thread:

UnityThread.executeCoroutine(myCoroutine());

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

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

Finally, if you don't need to execute anything in the LateUpdate and FixedUpdate functions, you should comment both lines of this code below:

//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK

这将提高性能.

这篇关于从另一个线程使用 Unity API 或调用主线程中的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆