从另一个线程使用Unity API或调用主线程中的函数

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

我不包括updatetext.GetComponent<Text>().text = "From server: "+tempMesg;的原因updatetext.GetComponent<Text>().text = "From server: "+tempMesg; 在无效getInformation是这个函数是在线程中,当我包含在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; 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安全的,所以他们决定通过添加一个机制来抛出一个exception,当其他Thread使用它的API时,他们决定不可能从另一个Thread调用它们的API。

这个问题已经被问了太多次了,但是没有任何解决scheme/答案。 答案通常是“使用插件”或做一些不是线程安全的。 希望这将是最后一个。

你通常在Stackoverflow或者Unity论坛网站上看到的解决scheme是简单地使用booleanvariables来让主线程知道你需要在主Thread执行代码。 这是不正确的,因为它不是线程安全的 ,不能控制提供哪个函数来调用。 如果你有多个需要通知主线程的线程呢?

你将看到的另一个解决scheme是使用一个协程,而不是一个Thread 。 这不起作用。 使用协程的套接字不会改变任何东西。 你仍然会遇到冻结问题。 你必须坚持你的Thread代码或使用Async

正确的方法之一是创build一个集合,如List 。 当您需要在主线程中执行某些内容时,请调用一个存储要在一个Action执行的代码的函数。 将该Action List复制到本地Action List ,然后执行该List中本地Action的代码,然后清除该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个最常用的Unity函数中调用函数: UpdateLateUpdateFixedUpdate函数。 这也允许你调用在主Thread运行一个协程函数。 它可以扩展到能够在其他Unitycallback函数(如OnPreRenderOnPostRender调用函数。

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

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

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

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

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

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

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

#2#3样品在Updatefunction中执行。

4.要从另一个Thread执行LateUpdate函数中的代码:

这是一个摄像头跟踪代码的例子。

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

5.要从另一个Thread执行FixedUpdate函数中的代码:

这个例子在做物理的时候比如给Rigidbody加力。

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

6.要从另一个主Thread启动主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 

这会提高性能。

这个例外的原因是UnityEngine不是线程安全的。 使用协程而不是线程:

 void Start () { updatetext.GetComponent<Text>().text = "Waiting..."; clientSocket.Connect("10.132.198.29", 8888); StartCoroutine(getInformation()); Debug.Log ("Running the client"); } // Update is called once per frame void Update () { updatetext.GetComponent<Text>().text = "From server: "+tempMesg; Debug.Log (tempMesg); } IEnumerator getInformation(){ while (true) { //... yield return null; if(done) break;//terminate condition } } 

把你的代码放在//...之前//...并且设置一个布尔值来表示信息何时准备就绪,方法应该终止。