从线程更改主线程上的GameObject

时间:2019-03-26 15:07:03

标签: c# multithreading unity3d interface listener

我想以某种方式从异步线程更改GameObject。我需要保留侦听器/接口的体系结构。

下面,我为当前的实现创建了一个简单的示例。

主脚本:

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

线程脚本:

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

监听器界面:

public interface IMyComponentListener
{
    void OnThreadCompleted();
}

如果我尝试这段代码,我将遇到以下错误:

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)

很明显,我无法从异步线程中更改主线程元素,但是如何解决该问题或正确实现支持此功能的体系结构?

2 个答案:

答案 0 :(得分:2)

这只是自您提出要求以来的一个例子。

通常,链接中的UnityMainThreadDispatcher很好。

无论如何,它只是通过Update方法来处理队列。我将不使用此Singleton-Pattern解决方案,而只是在您已经拥有的MonoBehaviour组件中做同样的事情(Singleton在大多数情况下只是一种快速但肮脏的解决方案)

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

    private MyComponent _myComponent;

    private ConcurrentQueue<Action> callbacks = new ConcurrentQueue<Action>();

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

    // Work the callbacks queue
    private void Update()
    {
        if(callbacks.Count == 0) return;

        while(callbacks.Count != 0)
        {
            Action a;
            if(!callbacks.TryDequeue(out a)) continue;

            a.Invoke();
        }
    }

    public void ThreadCompleted()
    {
        callbacks.Enqueue(OnThreadCompleted);
    }

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

与之相比,您将在该电话上呼叫enqueue,例如喜欢

((MyScript)_listener).ThreadCompleted();

问题可能是这里的类型转换。或者您可以将方法添加到界面中。


在您有多个侦听器的情况下(由于接口的原因),我也可能会使用您的方式,即使没有Singleton,也要使用适当的引用,例如通过检查器。


在智能手机上键入内容,因此没有保修,但我希望我能阐明我的观点

答案 1 :(得分:0)

我已经找到了使用外部组件UnityMainThreadDispatcher的解决方法。

遵循installation instructions之后,我已将调度程序更新为以下示例,并具有魅力!

线程脚本:

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