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

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

问题描述

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

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.

我在空getInformation中不包含updatetext.GetComponent<Text>().text = "From server: "+tempMesg;的原因是此函数在线程中,当我在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 can only be called from the main thread

我认为问题是我不知道如何在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,然后从该List中的本地Action执行代码,然后清除该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 示例在 Update 函数.

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

4 .要在 LateUpdate中执行代码来自另一个线程的功能:

4.To execute a code in the LateUpdate function from another Thread:

例如摄像机跟踪代码.

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

5 .要在 FixedUpdate中执行代码来自另一个线程的功能:

5.To execute a code in the FixedUpdate function from another Thread:

在进行物理操作(例如对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");
}

最后,如果您不需要执行 LateUpdate FixedUpdate 函数,您应该在下面的这段代码中注释两行:

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天全站免登陆