【问题标题】:Change GameObject at main thread from an Thread从 Thread 更改主线程的 GameObject
【发布时间】:2019-08-17 00:41:59
【问题描述】:

我想以某种方式从异步线程更改游戏对象。我需要保留侦听器/接口架构。

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

主脚本:

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)

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

【问题讨论】:

标签: c# multithreading unity3d interface listener


【解决方案1】:

我想出了一个使用外部组件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());
    }
}

【讨论】:

  • 你可以直接在MonoBehaviour 类中实现它,而不是使用单例模式。如果你另外使用ConcurrentQueue,你也可以去掉lock
  • @derHugo 你介意回答一个代码示例吗?
【解决方案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,而是使用适当的引用,例如通过 Inspector。


在我的智能手机上输入,所以没有保修,但我希望我能说清楚

【讨论】:

  • 不客气 :) 这个设置基本上对我有用,例如使用 TCP 客户端/服务器。优点是您现在可以禁用该行为并且队列操作系统不会进一步执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
相关资源
最近更新 更多