Search code examples
c#multithreadingunity-game-engineinterfacelistener

Change GameObject at main thread from an Thread


I would like somehow to change a GameObject from a async Thread. I need to keep the listener/interface architecture.

Below I've created a simple example of my current implementation.

Main script:

using UnityEngine;

public class MyScript : MonoBehaviour, IMyComponentListener
{
    public GameObject cube;

    private MyComponent _myComponent;

    private void Start()
    {
        _myComponent = new MyComponent(this);
    }

    public void OnThreadCompleted()
    {
        cube.transform.Rotate(Vector3.up, 10f);
    }
}

Thread Script:

using System.Threading;

public class MyComponent
{
    private readonly IMyComponentListener _listener;

    public MyComponent(IMyComponentListener listener)
    {
        _listener = listener;

        Thread myThread = new Thread(Run);

        myThread.Start();
    }

    private void Run()
    {
        Thread.Sleep(1000);

        _listener.OnThreadCompleted();
    }
}

Listener interface:

public interface IMyComponentListener
{
    void OnThreadCompleted();
}

If I try this code I will face the following error:

get_transform can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityEngine.GameObject:get_transform()
MyScript:OnThreadCompleted() (at Assets/Scripts/MyScript.cs:18)
MyComponent:Run() (at Assets/Scripts/MyComponent.cs:20)

Is clear I can't change main thread elements from a async Thread, but how do I workaround it or properly implement a architecture who supports this?


Solution

  • I've figured a workaround using a external component UnityMainThreadDispatcher.

    After follow the installation instructions I've updated my dispatcher to the example below and worked as charm!

    Thread script:

    using System.Threading;
    
    public class MyComponent
    {
        private readonly IMyComponentListener _listener;
    
        public MyComponent(IMyComponentListener listener)
        {
            _listener = listener;
    
            Thread myThread = new Thread(Run);
    
            myThread.Start();
        }
    
        private void Run()
        {
            Thread.Sleep(1000);
    
            UnityMainThreadDispatcher.Instance().Enqueue(() => _listener.OnThreadCompleted());
        }
    }