Can only be called from the main thread

Hi,

I use Firebase with Unity and I get errors like this ( in Visual Studio – Unity doesn’t throw any errors at all about this… ) when Firebase Database callbacks that all data is ready:

someTransfrom.position = UnityEngine.UnityException: get_position 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 mo…

It also happens for UI and other stuff… -however it worked in the past. Same code… only older ( final ) Unity version and older Firebase version.

I remember this from the past in other alphas/betas… -wonder why this happens…

Any idea when I’ll be fixed ?

Best
GreetinGs

This doesn’t seem to be Unity’s issue. Where are you calling those methods from? Your code probably executes on non-main thread at that point.

1 Like

I do something like this: Retrieving Data  |  Firebase Realtime Database → *Read data once-*section

Then I // Do something with snapshot… like to set an objects position or an uGUI element: I get the error mentioned above. BUT like I wrote… it worked fine before the Unity and Firebase update.

1 Like

So which version of Unity did it work in and which version of Unity does it not work in anymore?

I made an Unity and Firebase update in September 2018. It worked in Unity 2018.2.6 and Firebase 5.2.1.
Now I use Unity 2019.1b1 and Firebase 5.4.4. with .Net 4.x. and it’s broken.

Btw… the probably easiest way to get the error is to use this snippet within the Lamda expression:

try
{
     Debug.Log( Application.persistentDataPath );
}
catch (Exception exception)
{
     Debug.LogError(exception);
}

Does it work if you revert to Firebase 5.2.1 or if you try Firebase 5.4.4 with Unity 2018.2.6? I don’t think anything changed with threading on Unity side. If you think it did, please report a bug.

In either case, you’ll have to call the APIs you want from the right thread.

I already reported a bug and liked to this thread here.

I had to update the older Firebase version because I got compile errors in the new Unity version. And I don’t have Unity 2018 installed anymore.

I started a Firebase (Google) group thread.

I have had this before. Firebase’s implementation for Mono used to run its callbacks on the main thread. The one for .NET 4.x does not. It can be fixed by passing System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext() as the second argument to ContinueWith.

7 Likes

Thanks for the hint. I thought that the .Net 4.x version of Firebase fits best to the .Net 4.x version of Unity 2019.

Sadly that isn’t the case. I tried the .Net 3.x version of Firebase with the .Net 4.x version of Unity 2019 and now it works at least in the Editor.

It is actually better to have callbacks in a thread, that way you can do stuff that is thread safe, and then call to your main thread only when you need to.
I do something like:

ContinueWith( task =>  {
   // do what you can here in an alternate thread
   ..
   // based on the results, we need to do something on the Unity Thread:
   SyncContext.RunOnUnityThread( () => {
    // Do Unity stuff

});
});

An example of SyncContext might be:

public class SyncContext : Monobehaviour
{
    public static TaskScheduler unityTaskScheduler;
    public static int unityThread;
    public static SynchronizationContext unitySynchronizationContext;
    static public Queue<Action> runInUpdate= new Queue<Action>();
   

    public void Awake()
    {
        unitySynchronizationContext = SynchronizationContext.Current;
        unityThread = Thread.CurrentThread.ManagedThreadId;
        unityTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    }
    public static bool isOnUnityThread => unityThread == Thread.CurrentThread.ManagedThreadId;


    public static void RunOnUnityThread(Action action)
    {
        // is this right?
        if (unityThread ==Thread.CurrentThread.ManagedThreadId)
        {
            action();
        }
        else
        {
            lock(runInUpdate)
            {
                runInUpdate.Enqueue(action);
            }

        }
    }

  
 private void Update()
        {
            while(runInUpdate.Count > 0)
            {
                Action action = null;
                lock(runInUpdate)
                {
                    if(runInUpdate.Count > 0)
                        action = runInUpdate.Dequeue();
                }
                action?.Invoke();
            }

}
3 Likes

Thank you for the code. I’ll keep it in mind if Firebase .Net 3.x is not developed anymore in the far future.

1 Like

Thanks for the code. A question, what are you using unitySynchronizationContext and unityTaskScheduler for. I see they are not bing used in this class, so i assume the class is probably simplified, is it?

When I am lazy and I want everything to run in the unity thread I use something like this:

storageRef.PutStreamAsync(log,new MetadataChange() {ContentType="text/plain"}).ContinueWith((task) =>
            {
                if(task.IsFaulted)
                {
                    UGen.LogException(task.Exception);
                }
                else if(task.IsCompleted)
                {
                    var r = task.Result;
                    Debug.LogError($"UploadComplete: {r.SizeBytes/1024} kb {r.Path} ");
                }
                else
                {
                    Debug.LogError("Unknown result");
                }
                log.Dispose();
            },SyncContext.unityTaskScheduler);
1 Like

I just a similar problem when upgrading from Unity .net 3.5 to 4.x and Firebase 4. This problem seems to happen for UI related elements. You can add TaskScheduler.FromCurrentSynchronizationContext() as an argument for your ContinueWith function to fix the issue.

Example:

FirebaseDatabase.DefaultInstance
.GetReference("Leaders")
.GetValueAsync().ContinueWith(task => {
if (task.IsFaulted) {
// Handle the error...
}
else if (task.IsCompleted) {
DataSnapshot snapshot = task.Result;
// Do something with snapshot...
}
} ,TaskScheduler.FromCurrentSynchronizationContext() );
1 Like

You can use

using Firebase.Extensions;
+
ContinueWithOnMainThread()

and it will work !

source : Firebase.Extensions.TaskExtension Class Reference

16 Likes