简体   繁体   English

Unity3D,如何在正确的线程中处理事件

[英]Unity3D, how to process events in the correct thread

I'm writing a Unity3D script and using a networking library. 我正在编写Unity3D脚本并使用网络库。 The library emits events (calls delegates) when data is ready. 数据准备就绪时,库会发出事件(调用委托)。 My library reads that data and emits events which try to access GameObject but I get these errors 我的库读取该数据并发出试图访问GameObject事件,但我得到了这些错误

CompareBaseObjectsInternal 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.

ArgumentException: CompareBaseObjectsInternal 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.

That's fine. 没关系。 I think I understand that the network messages are being handled on a separate thread and Unity3D is trying to point that out and how it's not thread safe to go manipulating stuff from another thread. 我想我明白网络消息是在一个单独的线程上处理的,而Unity3D正在试图指出这一点以及如何从另一个线程中操纵东西不是线程安全的。

My question is: What's the recommended way to deal with this? 我的问题是:建议的方法是什么?

What I'm thinking is instead of calling the delegate directly in my handler I'll make some kind of object that references all the parameters delivered in the event and adds them to a List<MyEvent> of events. 我在想的不是直接在我的处理程序中调用委托,而是会创建一些引用事件中提供的所有参数的对象,并将它们添加到事件的List<MyEvent>中。

I'll have to make a thread safe wrapper class to contain that list that lets me add and remove events with a lock because List<> is not thread safe. 我将必须创建一个线程安全包装类来包含该列表,该列表允许我使用锁添加和删除事件,因为List<>不是线程安全的。

I'll then somehow need to process those events on the main thread. 然后我会以某种方式需要在主线程上处理这些事件。 I'm not sure what's the best way to do that. 我不确定最好的方法是什么。 The class that's dealing with the network is a MonoBehavior itself so I guess I can have its Update method remove events from the list and send them. 处理网络的类是MonoBehavior本身,所以我想我可以让它的Update方法从列表中删除事件并发送它们。

Does this sound like a good idea or is there some other, better, or simpler way to achieve this? 这听起来像个好主意还是有其他更好或更简单的方法来实现这一目标?

Here's what I ended up with. 这就是我最终的结果。

public class EventProcessor : MonoBehaviour {

    public void QueueEvent(Action action) {
        lock(m_queueLock) {
            m_queuedEvents.Add(action);
        }
    }

    void Update() {
        MoveQueuedEventsToExecuting();

        while (m_executingEvents.Count > 0) {
            Action e = m_executingEvents[0];
            m_executingEvents.RemoveAt(0);
            e();
        }
    }

    private void MoveQueuedEventsToExecuting() {
        lock(m_queueLock) {
            while (m_queuedEvents.Count > 0) {
                Action e = m_queuedEvents[0];
                m_executingEvents.Add(e);
                m_queuedEvents.RemoveAt(0);
            }
        }
    }

    private System.Object m_queueLock = new System.Object();
    private List<Action> m_queuedEvents = new List<Action>();
    private List<Action> m_executingEvents = new List<Action>();
}

The startup code in some other class adds one of these to the GameObject that started it. 其他类中的启动代码将其中一个添加到启动它的GameObject中。 The networking thread adds Action by calling eventProcessor.queueEvent and the Update function above ends up executing those actions on the main thread. 网络线程通过调用eventProcessor.queueEvent添加Action,上面的Update函数最终在主线程上执行这些操作。 I pull all the actions to another queue to keep things locked as little as possible. 我将所有操作都拉到另一个队列,以尽可能少地锁定内容。

I guess this wasn't that hard. 我想这不是那么难。 I was just wondering if there was some other recommended solution. 我只是想知道是否有其他推荐的解决方案。

I created a simple class for this reason that you can call with a one-liner. 我创建了一个简单的类,因为你可以使用单行调用。

You can use it like this: 你可以像这样使用它:

public IEnumerator ThisWillBeExecutedOnTheMainThread() {
    Debug.Log ("This is executed from the main thread");
    yield return null;
}
public void ExampleMainThreadCall() {
    UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread());
}

Simply head over to https://github.com/PimDeWitte/UnityMainThreadDispatcher and start using it if you'd like. 只需转到https://github.com/PimDeWitte/UnityMainThreadDispatcher并根据需要开始使用它。

Cheers 干杯

I cannot comment yet, so I'm adding an answer. 我还不能评论,所以我正在添加一个答案。

Your intuition is correct. 你的直觉是正确的。

You don't a class that handles the locking, is enough to just use a lock when adding to the list. 您没有处理锁定的类,只需在添加到列表时使用锁即可。

Using the Update method is the recommended way as it will always fire on the UI Thread so any event that gets fired is free to change the display list. 建议使用Update方法,因为它始终会在UI线程上触发,因此任何被触发的事件都可以自由更改显示列表。

If you want you could split the task of receiving the data from the task of firing the events by having a separate MonoBehaviour that just handles the dispatching of events. 如果您愿意,可以通过单独处理事件调度的MonoBehaviour来分割从触发事件的任务接收数据的任务。

You can create threads, however, don't do anything with a GameObject or any components of GameObjects in a separate thread. 您可以创建线程,但是,不要在单独的线程中对GameObject或GameObjects的任何组件执行任何操作。

But I really don't understand why do you need to use it, why can't you just use Coroutines? 但我真的不明白为什么你需要使用它,为什么你不能只使用Coroutines?

void SomeMethod() 
{
       StartCoroutine(CallNetworkCode());
       // ...keep going...
}

IEnumerator CallNetworkCode()
{
    bool done = false;
    var request = LibraryNetwork.DoSomeRequest(() => { done = true; });
    while (!done)
    {
        yield return 0; // wait for next frame
    }
    if (request.result != null)
        Debug.Log("ha!");
}

Or just assign the result resolution code to the delegate. 或者只是将结果解析代码分配给代理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM