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?
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With