10

I'm writing a Unity3D script and using a networking library. 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

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.

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.

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.

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.

Does this sound like a good idea or is there some other, better, or simpler way to achieve this?

Kristof U.
  • 1,263
  • 10
  • 17
gman
  • 100,619
  • 31
  • 269
  • 393
  • You're thinking along the right track. Rather than making a thread safe wrapper for the list, consider simply accessing the list within a lock (bit simpler). – NPSF3000 Mar 20 '14 at 02:30

4 Answers4

10

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. The networking thread adds Action by calling eventProcessor.queueEvent and the Update function above ends up executing those actions on the main thread. 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.

gman
  • 100,619
  • 31
  • 269
  • 393
  • I'm in the same boat. Did this solution prove to be good enough for you? Or did you find some other recommended way of doing it? – UpTheCreek Jan 08 '15 at 12:09
  • @gman I just came across another question today and my solution is similar to yours. You can improve this solution by adding a volatile boolean variable in the Update function. That will make sure that the the `lock` keyword is not being run every frame which results to a-little bit of performance hit. With my solution, you can run code in the `Update`, `FixedUpdate` and the `LateUpdate`. You can also start a coroutine in the main function. Take a [look how I did it](http://stackoverflow.com/questions/41330771/use-unity-api-from-another-thread-or-call-a-function-in-the-main-thread) – Programmer Dec 26 '16 at 18:38
4

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.

Cheers

Pim de Witte
  • 373
  • 2
  • 10
1

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.

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.

Radu Diță
  • 13,476
  • 2
  • 30
  • 34
  • "For firing the events you don't need a lock just to traverse the List, or any other data structure that you have, you can just loop the list and send the event." - Err.. I strongly suggest locking the list for reads as well as writes. – NPSF3000 Mar 20 '14 at 02:31
0

You can create threads, however, don't do anything with a GameObject or any components of GameObjects in a separate thread.

But I really don't understand why do you need to use it, why can't you just use 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.

Roberto
  • 11,557
  • 16
  • 54
  • 68
  • 1
    -1, There's nothing inherently wrong with using threads in Unity. – NPSF3000 Mar 20 '14 at 02:28
  • @NPSF3000 as far as I understand he's using a MonoBehaviour inside another thread, and that's something you really shouldn't do. Unity isn't thread safe and it will throw a lot of errors or crash if you do. – Roberto Mar 20 '14 at 02:55
  • @NPSF3000 yeah, but the way I wrote it people would understand to not use threads at all. I edited. – Roberto Mar 20 '14 at 02:58
  • Thanks for the suggestion. The library I'm using for networking delivers messages on threads. The object receiving the messages is not a MonoBehaviour though it is owned by one. – gman Mar 20 '14 at 05:16
  • 2
    I'm not the one making the threads. Some websocket library I'm using calls me with messages when they come in. They happen to be on other threads. I'd prefer not to have to write my own websocket library from scratch just to remove the threading. Thank you for the suggestion. – gman May 28 '14 at 06:13