Изменить GameObject в главном потоке из потока - PullRequest
0 голосов
/ 26 марта 2019

Я бы хотел как-то изменить 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 ]

2 голосов
/ 26 марта 2019

Это просто пример, так как вы просили об этом.

В общем случае 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, но вместо этого с правильными ссылками, например. через инспектора.


Набрано на моем смартфоне, поэтому никаких гарантий, но я надеюсь, что я ясно изложил свою точку зрения

0 голосов
/ 26 марта 2019

Я нашел обходной путь, используя внешний компонент UnityMainThreadDispatcher .

После следования инструкциям по установке Я обновил диспетчер до приведенного ниже примера и работалкак шарм!

Автор сценария:

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