Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Unity API's are not allowed in async callbacks?

I saw somewhere that within a Thread in Unity, you couldn't use Unity API's.

I'm wondering if this is also the case for async callbacks in general (for example a function assigned to WebSocket.OnMessage when using WebSocketSharp), and if so then is there a way to know what is allowed and what isn't (i.e. what are "Unity API's")?


As an example, when using WebSocketSharp's WebSocket.OnMessage, I put this in my Start function of a MonoBehavior:

// ws is a WebSocketSharp.WebSocket
// displayText is a UnityEngine.UI.Text

ws.OnMessage += (sender, evt) =>
  {
    // 1
    boo = UnityEngine.Random.Range(1, 1000).ToString();
    // 2
    displayText.text = "Heyoo";
  };

The line under 1 errors (no logs just beyond it) but no error message shows. Whereas when that line is not inside this callback (top-level of Update for example), I can see its result no problem.

As for the line under 2, the Inspector in Unity shows the updated text, but the Play screen does not, until I update an attribute in the Inspector, as if the text field did get updated, but when it needed to use a Unity API to update the screen, it failed, so it's not until a separate update happens that it actually appears.

That's my hypothesis for these odd behaviors, so please let me know if that is correct, and if there's a succinct (or documented) way to describe what I'm describing.

like image 270
tscizzle Avatar asked Sep 18 '25 05:09

tscizzle


1 Answers

async in general means exactly this: Not in the main thread.

It is hard to answer what is supported and what not in other threads then the mainthread ... short: Most things are not supported.

The UnityEngine.Random.Range(1, 1000).ToString(); should work. But be carefull with assignments!

A known workaround is to create like a callback worker and pass Actions to execute back to the main thread like e.g.:

public class MainThreadWorker : MonoBehaviour
{
    // singleton pattern
    public static MainThreadWorker Instance;

    // added actions will be executed in the main thread
    ConcurrentQueue<Action> actions = new ConcurrentQueue<Action>();

    private void Awake()
    {
        if (Instance)
        {
            this.enabled = false;
            return;
        }
        Instance = this;
    }

    private void Update()
    {
        // execute all actions added since the last frame
        while (actions.TryDequeue(out var action))
        {
            action?.Invoke();
        }
    }

    public void AddAction(Action action)
    {
        if(action != null) actions.Enqueue(action);
    }
}

Having this in your scene somewhere you can now pass an action back to the main thread like

ws.OnMessage += (sender, evt) =>
    {
        MainThreadWorker.Instance.AddAction(()=>
            {
                // 1
                boo = UnityEngine.Random.Range(1, 1000).ToString();
                // 2
                displayText.text = "Heyoo";      
            });
    };
like image 192
derHugo Avatar answered Sep 20 '25 21:09

derHugo