generic-serializable-dictionary icon indicating copy to clipboard operation
generic-serializable-dictionary copied to clipboard

InvalidOperationException: EnsureRunningOnMainThread can only be called from the main thread

Open Scorpin opened this issue 3 years ago • 19 comments

Hi, I get this error when working with your class. Until now it works fine, and I can't see where is the problem (I mean, I think I commented all the last changes before the error appears but still keep there...). Do you think there is some solution in your class? Here is the full stack error. Thank you.

InvalidOperationException: EnsureRunningOnMainThread can only be called from the main thread UnityEngine.Object.EnsureRunningOnMainThread () (at :0) UnityEngine.Object.GetInstanceID () (at :0) UnityEngine.Object.IsNativeObjectAlive (UnityEngine.Object o) (at :0) UnityEngine.Object.CompareBaseObjects (UnityEngine.Object lhs, UnityEngine.Object rhs) (at :0) UnityEngine.Object.op_Equality (UnityEngine.Object x, UnityEngine.Object y) (at :0) UnityEngine.Object.Equals (System.Object other) (at :0) System.Collections.Generic.ObjectEqualityComparer1[T].Equals (T x, T y) (at <c2a97e0383e8404c9fc0ae19d58f57f1>:0) System.Collections.Generic.Dictionary2[TKey,TValue].FindEntry (TKey key) (at :0) System.Collections.Generic.Dictionary2[TKey,TValue].ContainsKey (TKey key) (at <c2a97e0383e8404c9fc0ae19d58f57f1>:0) GenericDictionary2[TKey,TValue].ContainsKey (TKey key) (at Assets/Code/Libraries/GenericDictionary.cs:92) GenericDictionary`2[TKey,TValue].OnAfterDeserialize () (at Assets/Code/Libraries/GenericDictionary.cs:50)

Scorpin avatar Dec 21 '22 08:12 Scorpin

Hey thanks for reaching out!

Are you perhaps using the serializable dictionary from a threaded context? I know Unity's api is only allowed to be called from the main thread so by extension this dictionary has the same restriction unfortunately

upscalebaby avatar Dec 21 '22 08:12 upscalebaby

Thinking on this a little bit more it seems strange, since the dictionary is made using regular C# objects - so the Unity main thread restriction shouldn't apply to this object. I suspect I can fix this but I need to check it out closer, from the stack trace looks like the default equality comparer falls back to UnityEngine.Object.Equals which seems excessive.

upscalebaby avatar Dec 21 '22 08:12 upscalebaby

That's the problem, I'm not using any other threads or even co-routines code... The last change I made and I don't want to get it back so don't have to re-configure all the script, was adding a GenericDictionary with ints for keys and a MonoBehaviour class for value, and configured that on the scene, that's all...

Scorpin avatar Dec 21 '22 08:12 Scorpin

Okay well that explains the equality comparer checking for UnityEngine.Object.Equals so no issue there.

I suspect this is one of those hard to track down Unity bugs, since searching for the error message yields many threads of people with various random issues where the solution could be stuff like updating all packages to just restarting the editor or even computer.

My initial impression is that this isn't related to this dictionary but rather some other Unity problem. I could try helping you rule it out if you give me some more context but first make sure you've tried the stuff mentioned in the search results for this error message.

If that doesn't work out give me some info on what Unity Editor version you're running and some example code where I can try to reproduce it.

upscalebaby avatar Dec 21 '22 10:12 upscalebaby

I think this is the point where the error appears, on the OnAfterDeserialize. The Unity version we are using is the 2021.3.8f1: image image image

Scorpin avatar Dec 21 '22 11:12 Scorpin

And yes, I tried the solutions which some people used to fix the bug like restart Unity, the pc, and update all the packages (the only one using there was the TextMeshPro, and I have that one already updated, but I just updated all other packages of the project just in case), but still happens.

Scorpin avatar Dec 21 '22 11:12 Scorpin

Thanks for getting back, I'll have some time to reproduce this in a couple of hours and get back to you!

upscalebaby avatar Dec 21 '22 11:12 upscalebaby

Hey again! I cannot reproduce it. If I were you I would try to narrow down what's causing the error. I suspect it could be the TextMeshProUGUI values in the dictionary, but could be anything. So best bet is to backup your work in source control and then replace all object types with primitive types one by one, to check if the problem goes away and if so what caused it.

For instance

[SerializeField]
private GenericDictionary<int, TextMeshProUGUI> oldDatastructure;

Will become

[SerializeField]
private GenericDictionary<int, bool> newDatastructure;

and so on...

This should help narrow down the problem into exactly what is breaking it.

upscalebaby avatar Dec 21 '22 18:12 upscalebaby

Nothing... I tried to change those to bool but keep happening. I tried to change another file, a struct with a List of another struct, in case the recursive way were the problem, but keep happening. I even tried to debug it myself with a try-catch, in this way:

public bool ContainsKey(TKey key) {
    try {
        return dict.ContainsKey(key);
    }
    catch (Exception) {
        //Debug.Log("Key: " + key);
        throw new Exception("Exception when comparing: " + key);
    }
    
}

But this happens when I try to use any of both, the Debug class or launching a normal Exception:

UnityException: ToString 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.Object.ToString () (at <c6b52566f59b49fc861a7812a1ea2f6b>:0)
GenericDictionary`2[TKey,TValue].ContainsKey (TKey key) (at Assets/Code/Libraries/GenericDictionary.cs:98)
GenericDictionary`2[TKey,TValue].OnAfterDeserialize () (at Assets/Code/Libraries/GenericDictionary.cs:50)

Scorpin avatar Dec 21 '22 20:12 Scorpin

Ok... I found it... But it's very strange...

I put a Debug in the previous line of the Contains comparison, and appears this:

UnityException: ToString is not allowed to be called during serialization, call it from Awake or Start instead. Called from MonoBehaviour 'TestGameController' on game object 'System'.
See "Script Serialization" page in the Unity Manual for further details.
UnityEngine.Object.ToString () (at <c6b52566f59b49fc861a7812a1ea2f6b>:0)
UnityEngine.Logger.GetString (System.Object message) (at <c6b52566f59b49fc861a7812a1ea2f6b>:0)
UnityEngine.Logger.Log (UnityEngine.LogType logType, System.Object message) (at <c6b52566f59b49fc861a7812a1ea2f6b>:0)
UnityEngine.Debug.Log (System.Object message) (at <c6b52566f59b49fc861a7812a1ea2f6b>:0)
GenericDictionary`2[TKey,TValue].OnAfterDeserialize () (at Assets/Code/Libraries/GenericDictionary.cs:50)

So I went to that class, an old one which I didn't touched since like... 1-2 months, removed it from the System GameObject, and the error is gone... Those are all the serializable fields on that class:

public class TestGameController : MonoBehaviour {

    [SerializeField] private TextMeshProUGUI _coinsNumberTMP;
    [SerializeField] private GenericDictionary<GameObject, Sprite> _giftsDictionary;
    [SerializeField] private GameObject _giftBigGO;
    [SerializeField] private Sprite _giftBigSprite;

    public static TestGameController Instance { get; private set; }

    private void Awake() {

        if (Instance != null && Instance != this) {
            Destroy(this);
            return;
        }
        Instance = this;

    }

Some hint?

Scorpin avatar Dec 21 '22 20:12 Scorpin

Glad you found it! It's hard to tell what went wrong but presumably some corruption in Unity. Reimporting assets is a good way to flush corrupt data out but I've never heard of removing a class to fix these kinds of issues.

upscalebaby avatar Dec 21 '22 20:12 upscalebaby

Ok, can be.. If I can reproduce the bug anyway I'll advice you. Thank you for all :)

Scorpin avatar Dec 21 '22 20:12 Scorpin

Hello,

I'm having the exact same issue right now (never happened to me before). I'm on Unity 2022.2.1f1.

I'm having this bug AFTER creating a new project and patiently re-importing my assets (FMOD crashed my project, another story)

For me this bug happen each time I launch my game.

This is the errors messages I get :

InvalidOperationException: EnsureRunningOnMainThread can only be called from the main thread UnityEngine.Object.EnsureRunningOnMainThread () (at <bdd20210bb844b2e88e1149ea99da5ef>:0) UnityEngine.Object.GetInstanceID () (at <bdd20210bb844b2e88e1149ea99da5ef>:0) UnityEngine.Object.IsNativeObjectAlive (UnityEngine.Object o) (at <bdd20210bb844b2e88e1149ea99da5ef>:0) UnityEngine.Object.CompareBaseObjects (UnityEngine.Object lhs, UnityEngine.Object rhs) (at <bdd20210bb844b2e88e1149ea99da5ef>:0) UnityEngine.Object.op_Equality (UnityEngine.Object x, UnityEngine.Object y) (at <bdd20210bb844b2e88e1149ea99da5ef>:0) UnityEngine.Object.Equals (System.Object other) (at <bdd20210bb844b2e88e1149ea99da5ef>:0) System.Collections.Generic.ObjectEqualityComparer1[T].Equals (T x, T y) (at <8f06425e63004caf99a79845675f751e>:0) System.Collections.Generic.Dictionary2[TKey,TValue].FindEntry (TKey key) (at <8f06425e63004caf99a79845675f751e>:0) System.Collections.Generic.Dictionary2[TKey,TValue].ContainsKey (TKey key) (at <8f06425e63004caf99a79845675f751e>:0) GenericDictionary2[TKey,TValue].ContainsKey (TKey key) (at Assets/Scripts/GenericDictionary.cs:95) GenericDictionary2[TKey,TValue].OnAfterDeserialize () (at Assets/Scripts/GenericDictionary.cs:51) `

I have tried to catch the error but Unity is gently ignoring my request ☺

EDIT :

Ok, it seems to happen on a specific scene, where unfortunately I have a lot of non optionnal stuff (TextMeshPro notalby). Reinstalling textMeshPro doesn't change anything.

RE-EDIT :

I have flushed all my library folder but I doesn't change anything.

Aqueuse avatar Mar 22 '23 10:03 Aqueuse

Hey! Okay are you also using a static field, similar to op's field named TestGameController?

upscalebaby avatar Mar 22 '23 13:03 upscalebaby

Hey! Okay are you also using a static field, similar to op's field named TestGameController?

yes, but it's on my project since months and it has never throw this error (your dictionnary neither)

Aqueuse avatar Mar 22 '23 14:03 Aqueuse

I've kept this issue open since I suspected there was some issue with static initialization. I don't use singletons so haven't encountered this issue myself, but sounds like it's pretty tricky to reproduce since it doesn't occur immediately.

upscalebaby avatar Mar 22 '23 18:03 upscalebaby

Thank you for letting this open. I will continue to post if I find by myself how to solve that bug.

Aqueuse avatar Mar 23 '23 00:03 Aqueuse

Hello, I have succeed to isolate the problem, it was on a class where I use two Generic Dictionnary with Enum. I have manage to workaround the problem by moving them in a scriptableObject. I have absolutely no idea why but Unity is happy with that, no more warnings O_o Sadly, I havent figure out the precise cause of the problem. But at least, it could be a fix ? ¯\(°_o)/¯

Aqueuse avatar Mar 23 '23 16:03 Aqueuse

Wait, No, the warning is still here, but by intermittence now. I can trigger it by going on another scene then go back to my first scene with the generic dictionnary. But if I reload the scene, I don't have it like before and I don't have it when I launch the game.

Aqueuse avatar Mar 23 '23 16:03 Aqueuse