Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Unity API from another Thread or call a function in the main Thread

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.

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

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.

Here is my code:

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);         }     } } 
like image 527
user6142261 Avatar asked Dec 26 '16 11:12

user6142261


People also ask

What is the main thread in unity?

One thread runs at the start of a program by default. This is the “main thread”. The main thread creates new threads to handle tasks. These new threads run in parallel to one another, and usually synchronize their results with the main thread once completed.

Is Unity single threaded or multithreaded?

Starting with Unity 2020.2, that first conversion step is multi-threaded, prior to Unity 2020.2, it is single threaded.

Is unity debug log thread safe?

2 Replies. Debug. Log (as well as LogError, LogWarning, ...) are afaik the only methods that are actually threadsafe.


1 Answers

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.

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?

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.

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.

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 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;         }     } } 

USAGE:

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.First, initialize it from the Awake() function.

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

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

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

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.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)); } 

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

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

Example of this is a camera tracking code.

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

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

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

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

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"); } 

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 

This will increase performance.

like image 143
Programmer Avatar answered Nov 06 '22 02:11

Programmer