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

6

51

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);
        }
    }
}
Veedis answered 26/12, 2016 at 11:44 Comment(1)
#54207412 here here is my answer take a lookHammers
J
84

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.

Joule answered 26/12, 2016 at 15:58 Comment(17)
Sorry... I try to implement your solution... But when I type UnityThread.initUnityThread(); it shows the error that 'UnityThread' does not exist in the current context.. Sorry for new to unity...Could you explain your code in a more detailed way?... Many thanks..Veedis
executeCoroutine needs to be inside the #if (ENABLE_UPDATE_FUNCTION_CALLBACK) or else you are going to get a compiler error on the executeInUpdate(() => instance.StartCoroutine(action)); line when the symbol is not defined.Phagocytosis
@ScottChamberlain I actually did that on purpose so that if ENABLE_UPDATE_FUNCTION_CALLBACK is not defined, the function is not included. In this case, you won't mistakenly call an empty function that won't do anything resulting to days of troubleshooting about why it's not working by new programmers. It would be best to get an error if not defined. You think that's bad?Joule
"if ENABLE_UPDATE_FUNCTION_CALLBACK is not defined, the function is not included." That is the problem, what you said is not happening. The function public static void executeCoroutine(IEnumerator action) is before the #if block so if ENABLE_UPDATE_FUNCTION_CALLBACK is not defined the function executeCoroutine will still exist. I was trying to say you need to move the #if 12 lines higher so it is right before the COROUTINE IMPL comment so that both executeCoroutine and executeInUpdate no-longer exist when the symbol is not defined.Phagocytosis
I see what you are saying now. Thanks for the heads up. Let me know if there is another problem.Joule
Thanks @Joule to share this awesome solution! I'll give it a try to get the external ip (wan).Sipper
@Joule how to use method 3 with parameter like this void example(string a, int b)Canticle
If I understand you correctly, you are not using any threading stuff, aren't you? Why shouldn't I just use the Unity's main thread. I didn't see the advantage of using the normal unity's update method.Sipper
@Sipper I am sorry, I do not understand your statement and question. If you don't understand why this post is made, do not use it because it's likely you don't need it. If you are writing a code that runs in another Thread but want to use Unity's API in that Thread then this answer is for you.Joule
Ah, thanks for clarifying, now I understood your answer.Sipper
In a way, this post is all wrong. You don't do any of this when working in a frame-based ECS system.Chancey
For example, the "camera tracking AI" example is totally wrong. You just run the camera-tracking AI on another computer (or whatever) and very simply drop off the suggestions. The main thread then runs normally (in whatever way those programmers like .. late update, never update .. of no concern to the AI-system) and simply looks at the suggestions in question.Chancey
I've found wrapping the invoke() call in a try/catch clause helps throw errors that were previously hidden!Plant
@Chancey this post was made before ECS came outPlant
hi @palebone , hmm, there may be a misunderstanding. ECS just describes "game engines". (They are not OO, not object oriented and have no connection at all to the normal OO programming milieu. They're just "entity/components". It's a frame-based system.) Whilst Programmer is one of the best Programmers on here!, it's just fundamentally wrong. I explain why at vast length in my answer. All you do from other threads is "set values" - surprisingly it's easier than conventional threaded code.Chancey
Why not using SynchronizationContext.Current.Post((object state) => { YourFunction(); }, this);Koeppel
Saving SynchronizationContext.Current in Awake() and then using .Post as stated by Saleh worked for meMiso
S
15

I have been using this solution to this problem. Create a script with this code and attach it to a Game Object:

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using UnityEngine;

public class ExecuteOnMainThread : MonoBehaviour {

    public static readonly ConcurrentQueue<Action> RunOnMainThread = new ConcurrentQueue<Action>();
        
    void Update()
    {
        if(!RunOnMainThread.IsEmpty)
        {
           while(RunOnMainThread.TryDequeue(out var action))
           {
             action?.Invoke();
           }
        }
    }
}

Then when you need to call something on the main thread and access the Unity API from any other function in your application:

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {

    // Code here will be called in the main thread...

});
Shaneka answered 17/12, 2018 at 15:38 Comment(4)
Following @dynamoid advice I have updated the code. I have learned Queues by it self are not safe this way without lock. Still not sure about this approach. My main usage is collecting data from a c++ code that runs aside.Telluride
Wouldn't the RunOnMainThread need to be static in order to access it this way? ;) @EgemenErtuğrul why did you remove that?Worldlywise
@Worldlywise because Static class ExecuteOnMainThread' cannot derive from type UnityEngine.MonoBehaviour'. Static classes must derive from objectNimrod
@Nimrod I am not talking about the class type ExecuteOnMainThread which as we can see is not static and derives from MonoBehaviour but about the member RunOnMainThread which can only be accessed via type itself if it is static which is the intended way in the shown use case .. an edit had removed thatWorldlywise
C
11

Much of the writing about threads in Unity is incorrect.

How so?

Unity is, of course, totally frame-based.

When you work in a frame-based system, threading issues are completely different.

Threading issues on a frame-based system are completely different. (In fact, often much easier to deal with.)

Let's say you have a Unity thermometer display that shows some value

Thermo.cs

enter image description here

So it will have a function which is called in Update, like

func void ShowThermoValue(float fraction) {
   display code to show the current thermometer value
}

Recall that the "Update" function in Unity simply means "run this once each frame".

That only runs once per frame, and that's that.

(Naturally, it runs only on the "main thread". There's nothing else in Unity! There's just ... "the Unity thread"!)

Somewhere else, perhaps in "IncomingData.cs", you will have a function which handles the concept "a new value has arrived":

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

    ... ???
}

Note that, of course, that is a class function! What else can it be?

You can't "reach in to" a normal Unity function. (Such as ShowThermoValue.) That would be meaningless - it's just a function which runs once each frame.Footnote 1

Let's say: values arrive very frequently and irregularly.

Image you have some sort of scientific devices (perhaps IR thermometers) connected connected to a rack of PCs

Those electronic devices deliver new "temperature" values very often. Let's say dozens of times per frame.

So, "NewValueArrives" is being called 100s of times a second.

So what do you do with the values?

It could not be simpler.

From the arriving-values thread, all you do is ................. wait for it ............. set a variable in the component!!

WTF? All you do is set a variable? That's it? How can it be that simple?

This is one of those unusual situations:

  1. Much of the writing on threads in Unity is, simply, completely hopeless.

  2. Surprisingly, the actual approach is extremely simple.

  3. It's so simple you may think you are doing something wrong!!

So have the variable ...

[System.Nonserialized] public float latestValue;

Set it from the "arriving thread" ...

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

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

Honestly that's it.

Essentially, to be the world's greatest expert at "threading in Unity" - which is, obviously, frame-based - there's nothing more to do than the above.

And whenever ShowThermoValue is called each frame ...................... simply display that value!

Really, that's it!

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

You're simply displaying the "latest" value.

latestValue may have been set once, twice, ten times, or a hundred times that frame ............ but, you simply display whatever is the value when ShowThermoValue runs that frame!

What else could you display?

The thermometer is updating at 60fps on-screen so you display the latest value. Footnote 2

It's actually that easy. It's just that easy. Surprising but true.


#(Critical aside - don't forget that vector3, etc, are NOT Atomic in Unity/C#)

As user @dymanoid has pointed out (read the important discussion below) it's critical to remember that while float is atomic in the Unity/C# milieu, anything else (say, Vector3 etc) IS NOT ATOMIC. Typically (as in the example here) you only pass floats around from calculations from, say, native plugins, thermometers etc. But it's essential to be aware that vectors and so on are NOT atomic.


Sometimes experienced threading programmers get in a knot with a frame-based system, because: in a frame based system most of the problems caused by racetrack and locking issues ... do not exist conceptually.

In a frame-based system, any game items should simply be displaying or behaving based on some "current value," which is set somewhere. If you have info arriving from other threads, just set those values - you're done.

You can not meaningfully "talk to the main thread" in Unity because that main thread ............. is frame-based!

Most locking, blocking and racetrack issues are non-existent in the frame-based paradigm because: if you set latestValue ten times, a million times, a billion times, in one particular frame .. what can you do? .. you can only display one value during that frame!

Think of an old-fashioned plastic film. You literally just have ...... a frame, and that's it. If you set latestValue a trillion times in one particular frame, ShowThermoValue will simply display (for that 60th of a second) the one value it grabs when it is run.

All you do is: leave information somewhere, which, the frame-paradigm system will utilize during that frame if it wants to.

That's it in a nutshell.

Thus, most "threading issues" disappear in Unity.

All you can do from

  • other calculation threads or

  • from plugin threads,

is just "drop-off values" which the game may use.

That's it!

Let's consider the question title...

How do you "... call a function in the main Thread"

This is completely meaningless. The "functions" in Unity are simply functions which the frame engine runs once per frame.

You can't "call" anything in Unity. The frame engine runs a number of things (many things) once per frame.

Note that indeed threads are totally irrelevant. If Unity ran with a billion threads, or with quantum computing, it would have no bearing on anything.

You can't "call a function" in a frame-based system.

Fortunately, the approach to take is dead simple, you just set values, which the frame-based functions can look at when they want! It's really that easy.


Footnotes


1 How could you? As a thought experiment, forget about the issue that you're on a different thread. ShowThermoValue is run once a frame by the frame engine. You can't "call" it in any meaningful way. Unlike in normal OO software, you cannot, say, instantiate an instance of the class (a Component?? meaningless) and run that function - that is completely meaningless.

In "normal" threaded programming, threads can talk back and fore and so on, and in doing so you have concerns about locking, racetrack and so on. But that is all meaningless in an ECS, frame-based, system. There is nothing to "talk to".

Let's say that Unity was in fact multithreaded!!!! So the Unity guys have all of the engine running in a multithreaded manner. It wouldn't make any difference - you can not get "in to" ShowThermoValue in any meaningful way! It's a Component which the frame engine runs once a frame and that's that.

So NewValueArrives is not anywhere - it's a class function!

Let's answer the question in the headline:

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

The concept is >> completely meaningless <<. 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 on one of the frames.

enter image description here

Of course that is meaningless. All you can do is change something for the next photo, the next frame.


2 I refer to the "arriving-values thread" ... in fact! NewValueArrives may, or may not, run on the main thread!!!! It may run on the thread of the plugin, or on some other thread! It may actually be completely single-threaded by the time you deal with the NewValueArrives call! It just doesn't matter! What you do, and all you can do, in a frame-based paradigm, is, "leave laying around" information which Components such as ShowThermoValue, may use, as, they see fit.

Chancey answered 14/1, 2019 at 15:31 Comment(6)
Comments are not for extended discussion; this conversation has been moved to chat.Howerton
An admin has unfortunately deleted a critical technical discussion here. User @ dymanoid has pointed out that floats are atomic in the Unity/C# milieu but be aware that things like (say) Vector3 are not atomic in the Unity/C# milieu.Chancey
@Fattie, I also pointed out that multithreading issues are not only about race conditions (where atomic operations can be of importance). There is a bunch of other multithreading pitfalls like e.g. instruction reordering or memory barriers, and all of them can easily happen in Unity. Thus, Unity must cope with all well-known multithreading issues. Your statement "Threading issues on Unity are different, certain concepts simply do not exist" is incorrect and misleading, so your answer too. Unity is based on a .NET runtime (Mono), and all rules of a .NET runtime apply.Jarv
@Jarv , thanks, I'm, not in to a long discussion, but exactly as I said "certain concepts simply do not exist" and as I also said "Threading issues on Unity are different". (You've pointed out some some issues which do exist, and that's of course true, but it's not the thrust of this QA.) The bottom line is, consider my friend Programmer's answer above - it's actually just wrong - you just don't do anything even vaguely like that in Unity; what you do is simply and literally just as in my thermometer example ("NewValueArrives" etc).Chancey
If you "drop off a value" into a list, you will frequently throw ConcurrentModificationException.Vharat
This solution only applies for a very small selection of problems, particularly ones where the only task is to get information from a different thread to the main thread. Many situations however also require the inverse interaction, getting information from the main thread to another thread. This is also the case in the question, because they want to call getComponent<>(). In this particular situation you may be able to work around this, but in general that is the greater problem.Afforest
C
10

Another solution to run code on the main thread, but without requiring a game object and MonoBehavior, is to use SynchronizationContext:

// On main thread, during initialization:
var syncContext = System.Threading.SynchronizationContext.Current;

// On your worker thread
syncContext.Post(_ =>
{
    // This code here will run on the main thread
    Debug.Log("Hello from main thread!");
}, null);
Contemplate answered 2/8, 2021 at 8:20 Comment(1)
This works fine on Unity IOS platform but thread is null on Unity Android platform.Killdeer
P
1

In Unity versions 2023.1+ you can do:

await Awaitable.MainThreadAsync();
// GetComponent or somethin'
await Awaitable.BackgroundThreadAsync();
Prothallus answered 5/2 at 11:2 Comment(0)
V
0

Use UniRx's multithreading pattern, UniTask and RxSocket together.

[SerializeField] private Text m_Text;

async UniTaskVoid Connect() {
    IPEndPoint endPoint = new IPEndPoint(IPAddress.IPv6Loopback, 12345);

    // Create a socket client by connecting to the server at the IPEndPoint.
    // See the UniRx Async tooling to use await 
    IRxSocketClient client = await endPoint.ConnectRxSocketClientAsync();

    client.ReceiveObservable
        .ToStrings()
        .ObserveOnMainThread()
        .Subscribe(onNext: message =>
    {
        m_Text.text = message;
    }).AddTo(this);

    // Send a message to the server.
    client.Send("Hello!".ToByteArray());
}
Vharat answered 2/1, 2020 at 7:34 Comment(3)
I'm really sorry but the base concept just doesn't work on a frame-based system. UniRx and UniTask are completely misguided. {Just for example, on UniRx, we see the sentence "IEnumerator (Coroutine) is Unity's primitive asynchronous tool" - which is just totally and completely incorrect. Coroutines in Unity have no relationship, in any way, at all, to threading or asynchronicity than the ocean to golf :) My incredibly simple "thermometer" example above completely explains how to actually do "this type of thing" in Unity.Chancey
Are you reading this comment and learning Unity? Try to learn the packages and patterns demonstrated here, they demonstrate expertise. This solution is the most correct and succinct, and correctly implements the ideas in the other comments. You will adopt practices like reactive programming that you can use in many applications and across languages.Vharat
Agree with @DoctorPangloss. Also seems unnatural if you are not used to work with Rx but make completelly sense to meMicronutrient

© 2022 - 2024 — McMap. All rights reserved.