Search Unity

Can only be called from the main thread

Discussion in '2019.1 Beta' started by Jokerminator, Feb 1, 2019.

  1. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    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
     
    Last edited: Feb 1, 2019
  2. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,781
    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.
     
  3. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    I do something like this: https://firebase.google.com/docs/database/unity/retrieve-data -> 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.
     
    Last edited: Feb 1, 2019
  4. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,781
    So which version of Unity did it work in and which version of Unity does it not work in anymore?
     
  5. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    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:
    Code (CSharp):
    1.  
    2. try
    3. {
    4.      Debug.Log( Application.persistentDataPath );
    5. }
    6. catch (Exception exception)
    7. {
    8.      Debug.LogError(exception);
    9. }
    10.  
     
    Last edited: Feb 4, 2019
  6. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,781
    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.
     
  7. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    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.
     
    Last edited: Feb 3, 2019
  8. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    699
    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.
     
  9. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    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.
     
  10. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    135
    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:

    Code (CSharp):
    1. ContinueWith( task =>  {
    2.    // do what you can here in an alternate thread
    3.    ..
    4.    // based on the results, we need to do something on the Unity Thread:
    5.    SyncContext.RunOnUnityThread( () => {
    6.     // Do Unity stuff
    7.  
    8. });
    9. });
    An example of SyncContext might be:

    Code (CSharp):
    1. public class SyncContext : Monobehaviour
    2. {
    3.     public static TaskScheduler unityTaskScheduler;
    4.     public static int unityThread;
    5.     public static SynchronizationContext unitySynchronizationContext;
    6.     static public Queue<Action> runInUpdate= new Queue<Action>();
    7.    
    8.  
    9.     public void Awake()
    10.     {
    11.         unitySynchronizationContext = SynchronizationContext.Current;
    12.         unityThread = Thread.CurrentThread.ManagedThreadId;
    13.         unityTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    14.     }
    15.     public static bool isOnUnityThread => unityThread == Thread.CurrentThread.ManagedThreadId;
    16.  
    17.  
    18.     public static void RunOnUnityThread(Action action)
    19.     {
    20.         // is this right?
    21.         if (unityThread ==Thread.CurrentThread.ManagedThreadId)
    22.         {
    23.             action();
    24.         }
    25.         else
    26.         {
    27.             lock(runInUpdate)
    28.             {
    29.                 runInUpdate.Enqueue(action);
    30.             }
    31.  
    32.         }
    33.     }
    34.  
    35.  
    36.  private void Update()
    37.         {
    38.             while(runInUpdate.Count > 0)
    39.             {
    40.                 Action action = null;
    41.                 lock(runInUpdate)
    42.                 {
    43.                     if(runInUpdate.Count > 0)
    44.                         action = runInUpdate.Dequeue();
    45.                 }
    46.                 action?.Invoke();
    47.             }
    48.  
    49. }
     
  11. Jokerminator

    Jokerminator

    Joined:
    Jun 30, 2006
    Posts:
    258
    Thank you for the code. I'll keep it in mind if Firebase .Net 3.x is not developed anymore in the far future.
     
    DavidSWu likes this.
  12. lsierra12

    lsierra12

    Joined:
    Nov 26, 2017
    Posts:
    5
    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?
     
  13. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    135
    When I am lazy and I want everything to run in the unity thread I use something like this:
    Code (CSharp):
    1. storageRef.PutStreamAsync(log,new MetadataChange() {ContentType="text/plain"}).ContinueWith((task) =>
    2.             {
    3.                 if(task.IsFaulted)
    4.                 {
    5.                     UGen.LogException(task.Exception);
    6.                 }
    7.                 else if(task.IsCompleted)
    8.                 {
    9.                     var r = task.Result;
    10.                     Debug.LogError($"UploadComplete: {r.SizeBytes/1024} kb {r.Path} ");
    11.                 }
    12.                 else
    13.                 {
    14.                     Debug.LogError("Unknown result");
    15.                 }
    16.                 log.Dispose();
    17.             },SyncContext.unityTaskScheduler);
     
    lsierra12 likes this.
  14. nhic

    nhic

    Joined:
    Dec 25, 2018
    Posts:
    3
    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:

    Code (CSharp):
    1. FirebaseDatabase.DefaultInstance
    2. .GetReference("Leaders")
    3. .GetValueAsync().ContinueWith(task => {
    4. if (task.IsFaulted) {
    5. // Handle the error...
    6. }
    7. else if (task.IsCompleted) {
    8. DataSnapshot snapshot = task.Result;
    9. // Do something with snapshot...
    10. }
    11. } ,TaskScheduler.FromCurrentSynchronizationContext() );
     
    Fangh likes this.
  15. Fangh

    Fangh

    Joined:
    Apr 19, 2013
    Posts:
    16