Search Unity

[Solved] Bizarre Error: UnityException: get_gameObject can only be called from the main thread.

Discussion in 'Scripting' started by LandonC, Jul 10, 2018.

  1. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    A very bizarre error has just occurred on my Android game when I'm trying to intergrate Appodeal ad plugin.

    In the ad callback, a function will be called when the ad has finished showing. The error occurs in the function that was called.

    This is the error:

    UnityException: get_gameObject 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.
    at MainUi.EndLoading () [0x00012] in MainUi.cs:510


    Basically the error points to UnityEngine.gameObject that needs to be disabled.

    Code (CSharp):
    1. public RectTransform instLoading;
    2.  
    3. public void EndLoading()
    4.     {
    5.         if(loading)
    6.         {
    7.             instLoading.gameObject.SetActive(false);
    8.             loading = false;
    9.         }
    10.     }    

    It is just a public GameObject that is already attached on the inspector. This error only occurs when I am testing on my device.

    I don't even know what difference does it make if I did anything to it on Start or Awake.

    I tried using the function on its own and it works without any error. The error only happens when it is called inside onRewardedVideoClosed() which is the callback provided in the Appodeal API.

    The weirdest thing is, the appodeal plugin was actually working like a charm when I first tested. This catastrophe happened suddenly and I’m very frustrated.

    Please do help if you have any suggestions to solve this issue.

    I've done some searching and these are my findings, unfortunately, they only provided me with some hints. So far I have yet to make any progress with the error.

    https://answers.unity.com/questions/7524/is-the-unity-api-threadsafe.html
    https://answers.unity.com/questions/31429/argumentexception-getvalue-can-only-be-called-from.html
    https://answers.unity.com/questions/995185/applicationloadlevel-argumentexception.html
    https://gamedev.stackexchange.com/q...nly-be-called-from-the-main-thread-error-in-m
     
    hhoffren and roointan like this.
  2. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    Alright, I did some more searching and found these:

    https://forum.unity.com/threads/setactive-can-only-be-called-from-the-main-thread.469486/
    https://forum.unity.com/threads/setactive-can-only-be-called-from-the-main-thread.298962/
    https://answers.unity.com/questions/180243/threading-in-unity.html
    https://answers.unity.com/questions/899301/asynchronous-threads-in-unity-3d.html
    https://forum.unity.com/threads/async-await-not-joining-main-thread-after-await.481212/

    ...and since I'm not familiar with threading and async methods, I overcome this error by starting a while loop in a coroutine when fetching the ad, and then wait for a simple bool response from the callback. It's not the ideal way to do it in my opinion but it works for now.

    For those who have a better way to deal with this, please do shed some light. :)
     
    hhoffren likes this.
  3. tsibiski

    tsibiski

    Joined:
    Jul 11, 2016
    Posts:
    604
    Until better support for Async and Multithreading is added to Unity, no Unity API's allow you to access them outside fo the main thread, as you've just seen.

    Queuing behaviors is the way to do this. When Async threads return something, push the information into some job queue you've made, and you would then have a process pick up that work that needs to be done.

    It sounds sort of like what you are saying, but I wouldn't put that in a while loop, IMO. I'd put that in Update, or some other method called each frame.

    You really wouldn't even need a job system if this is a one off thing. I'd simply set loading = false in that method. Then, in an Update method, have it checking for a time when loading != true && gameobject is not active. And when it doesn't anymore, set the gameobject to active.
     
  4. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    Ah! Thanks for your input! I'm very cautious of putting methods in Update() ever since I saw spikes popping out of the profiler that I moved all my methods in Update to Coroutines. It just worries me that it is constantly being checked that it might cause some lag. May I know why would you prefer to use Update and is my concern valid at all, what could be the drawbacks of using Coroutines in this case? Please pardon the terms I used if they are wrong, I do not come from a programming background. :)
     
  5. tsibiski

    tsibiski

    Joined:
    Jul 11, 2016
    Posts:
    604

    You have absolutely nothing to worry about performance-wise for the above workaround. The only time that Update can cause lag is when you have some super-heavy calculations in there - especially if that logic does not need to be run every single frame.

    In an oversimplified way, you can compare your Update logic to a coroutine by saying "How much logic did I run in one go before I put a 'yield return' statement. If you have all of the same logic without breaking it up in between with a 'yield return', then you have done nothing differently than a Coroutine would do for the same logic.

    The key, I think, is to put logic that is simple and state-changing in Update. For example, if you need to update an object's position in the world. But if you need to do heavy calculations, such as comparing a ton of objects to each other and performing heavy calculations - then it makes sense to put those in IEnumerators, and just launch them off as needed (with yield returns in between each major calculation).

    If you ever doubt if your code belongs in an Update, I recommend putting a break point at the start of an update and at the end of an update. If it is essentially instantaneous when you continue the breakpoint, then you are in a good situation.

    Believe it or not, putting too much logic into Coroutines can cause the same lag too. Some people, me included (when I first started learning about them), do not realize exactly how Coroutines work. It is a simulation of asynchronocity - rather than actually being multi-threaded. Since the application basically shuffles the main thread between all active coroutines, allowing them to execute up until the next yield statement, before passing the thread to the next coroutine - if you do not manage the number of coroutines you use simultaneously (and how you use them), you can find yourself in the exact same problem as Updates with too much heavy logic.
     
    Last edited: Jul 11, 2018
    LandonC likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    This actually did not help you in any way, despite what you might think. It's a very common myth in Unity that Update() is somehow bad.

    You can see here that basically Coroutines are "pumped" immediately after all Update() functions are pumped.

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    Scroll down to the section on Game Logic. It's all just code, it is all serially executed on the main thread (in this case).
     
    LandonC likes this.
  7. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    Thank you so much for your detailed explanation! :D It is very much appreciated, I think I know what to do in the future after reading this. I didn't quite like using coroutines too much, it becomes a headache when I try to debug to trace where was the coroutine called.
     
    tsibiski likes this.
  8. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    Thank you for the link! That's a very useful chart!

    While reading from the top I notice one thing:

    OnLevelWasLoaded: This function is executed to inform the game that a new level has been loaded.

    Isn't this depreciated? Or am I not aware that it is "revived".
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Oooh, nice catch. I reported it on their page. Good eye. Maybe it is still called so they left it in? I haven't used it in a while so I dunno if it is maybe still called, despite being deprecated.
     
    LandonC likes this.
  10. Deleted User

    Deleted User

    Guest

    I have found a workaround for this problem. Try it.

    Code (CSharp):
    1.  
    2. public class AdManager : MonoBehaviour
    3. {
    4.     bool EligibleReward = false;
    5.  
    6.     public void ShowRewardedAd()
    7.     {
    8.         Appodeal.show(Appodeal.REWARDED_VIDEO);
    9.     }
    10.  
    11.     private void RewardPlayer()
    12.     {
    13.         //TODO reward the player
    14.     }
    15.  
    16.     //Auto-called when video ad closes.
    17.     public void onRewardedVideoClosed(bool finished)
    18.     {
    19.         EligibleReward = finished;
    20.     }
    21.  
    22.     //Auto-called AFTER onRewardedVideoClosed is called. This method is documented on Unity website.
    23.     private void OnApplicationPause(bool pause)
    24.     {
    25.         if (EligibleReward && !pause)
    26.         {
    27.             RewardPlayer();
    28.             EligibleReward = false;
    29.         }
    30.     }
    31. }
    32.  
     
    Last edited by a moderator: Jul 13, 2018
  11. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83

    Wow! I'll give it a try and tell you if it works!
     
  12. Deleted User

    Deleted User

    Guest

    Take your time.

    About this problem, it seems that we can't access any GameObject and Monobehaviour from onRewardedVideoClosed method, except its own, or the same error will pop up. When the video ad closes and the game resumes, the method was called before OnApplicationPause(false). I think there is a limitation on what not to put in the method.
     
    ritesh_khokhani likes this.
  13. LandonC

    LandonC

    Joined:
    Dec 20, 2012
    Posts:
    83
    I tried and it works! Thanks so much for your help!
     
  14. IsraelShalLogic

    IsraelShalLogic

    Joined:
    Sep 4, 2015
    Posts:
    4
    Thank you !!!


     
  15. MaxUnity111

    MaxUnity111

    Joined:
    Mar 25, 2019
    Posts:
    11
    rewaed_error.png Greetings, I built AdMob into the game and got an error, I've been suffering with it for a week now.Tell me what to do about it?

    This code hangs on the button prefab, which is located on two different panels:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using GoogleMobileAds.Api;
    6. using UnityEngine.Monetization;
    7. using System;
    8.  
    9. public class MineBtn : MonoBehaviour
    10. {
    11.     public bool admob = true;
    12.     private FieldFillingManager ffm;
    13.     private double amount = 0;
    14.  
    15. # if UNITY_ANDROID
    16.     private const string ad_reward_diamonds = "ca-app-pub-9578413026007667/3802113406";
    17.     private const string ad_reward_filedPanel = "ca-app-pub-9578413026007667/8330371577";
    18.     private const string test_adID = "ca-app-pub-3940256099942544/5224354917";
    19.     private string admob_ID;
    20.     private RewardedAd rewardedAdDiamonds;
    21.     private RewardedAd rewardedAdFiled;
    22.  
    23. #endif
    24.  
    25.     void Start()
    26.     {    
    27.         ffm = FindObjectOfType<FieldFillingManager>();
    28. #if UNITY_ANDROID    
    29.  
    30.         if (admob)
    31.         {
    32. //            MobileAds.Initialize("ca-app-pub-9578413026007667~1116240310");  moved to
    33. //                                                                                                                             FieldFillingManager
    34.             if (GetComponentInParent<FailedPanel>())
    35.             {
    36.                 admob_ID = ad_reward_filedPanel;
    37.                 this.rewardedAdFiled = CreateAndLoadRewardedAd(test_adID);//ad_reward_filedPanel);
    38.             }
    39.             else
    40.             {
    41.                 admob_ID = ad_reward_diamonds;
    42.                 this.rewardedAdDiamonds = CreateAndLoadRewardedAd(test_adID);// ad_reward_diamonds);
    43.             }
    44.         }
    45. #endif
    46.     }
    47.  
    48.     public RewardedAd CreateAndLoadRewardedAd(string adUnitId)
    49.     {
    50.         RewardedAd rewardedAd = new RewardedAd(adUnitId);
    51.  
    52.         rewardedAd.OnUserEarnedReward += HandleUserEarnedReward;
    53.         rewardedAd.OnAdClosed += HandleRewardedAdClosed;
    54.  
    55.      
    56.        // AdRequest request = new AdRequest.Builder().AddTestDevice(AdRequest.TestDeviceSimulator).AddTestDevice("E2940A62C1B655BA").Build();
    57.        // AdRequest request = new AdRequest.Builder().AddTestDevice("E2940A62C1B655BA").Build();
    58.  
    59.        AdRequest request = new AdRequest.Builder().Build();
    60.    
    61.         rewardedAd.LoadAd(request);
    62.         return rewardedAd;
    63.     }
    64.  
    65.     public void ButtonClick()
    66.     {
    67.      
    68. #if UNITY_ANDROID
    69.      
    70.         if (admob)
    71.         {
    72.             if (GetComponentInParent<FailedPanel>())
    73.             {
    74.                 if (rewardedAdFiled.IsLoaded())
    75.                 {
    76.                     rewardedAdFiled.Show();
    77.                 }
    78.             }
    79.             else
    80.             {
    81.                 if (rewardedAdDiamonds.IsLoaded())
    82.                 {
    83.                     rewardedAdDiamonds.Show();
    84.                 }
    85.             }
    86.         }
    87.  
    88. #endif
    89.     }
    90. #if UNITY_ANDROID
    91.  
    92.  
    93.  
    94.     public void HandleRewardedAdClosed(object sender, EventArgs args)
    95.     {    
    96.  
    97.         if (GetComponentInParent<FailedPanel>())
    98.         {
    99.             this.rewardedAdFiled = CreateAndLoadRewardedAd(test_adID);//ad_reward_filedPanel);
    100.         }
    101.         else
    102.         {
    103.             this.rewardedAdDiamonds = CreateAndLoadRewardedAd(test_adID);//ad_reward_diamonds);
    104.         }
    105.         Debug.Log("HandleRewardedAdClosed");
    106.     }
    107.  
    108.     public void HandleUserEarnedReward(object sender, Reward args)
    109.     {
    110.         string type = args.Type;
    111.         amount = args.Amount;
    112.         ffm.SetDiamonds(ffm.GetDiamonds() + (int)amount);
    113.         Debug.Log("UserEarnedReward");
    114.     }
    115.  
    116. #endif
    117. }
     
    Last edited: Mar 13, 2020
  16. MaxUnity111

    MaxUnity111

    Joined:
    Mar 25, 2019
    Posts:
    11
    Greetings, I built AdMob into the game and got an error, I've been suffering with it for a week now.Tell me what to do about it?

    rewaed_error.png

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using GoogleMobileAds.Api;
    6. using UnityEngine.Monetization;
    7. using System;
    8.  
    9. public class MineBtn : MonoBehaviour
    10. {
    11.     public bool admob = true;
    12.     private FieldFillingManager ffm;
    13.     private double amount = 0;
    14.  
    15. # if UNITY_ANDROID
    16.     private const string ad_reward_diamonds = "ca-app-pub-9578413026007667/3802113406";
    17.     private const string ad_reward_filedPanel = "ca-app-pub-9578413026007667/8330371577";
    18.     private const string test_adID = "ca-app-pub-3940256099942544/5224354917";
    19.     private string admob_ID;
    20.     private RewardedAd rewardedAdDiamonds;
    21.     private RewardedAd rewardedAdFiled;
    22.  
    23. #endif
    24.  
    25.     void Start()
    26.     {    
    27.         ffm = FindObjectOfType<FieldFillingManager>();
    28. #if UNITY_ANDROID    
    29.  
    30.         if (admob)
    31.         {
    32. //            MobileAds.Initialize("ca-app-pub-9578413026007667~1116240310");  перенес в FieldFillingManager
    33.             if (GetComponentInParent<FailedPanel>())
    34.             {
    35.                 admob_ID = ad_reward_filedPanel;
    36.                 this.rewardedAdFiled = CreateAndLoadRewardedAd(test_adID);//ad_reward_filedPanel);
    37.             }
    38.             else
    39.             {
    40.                 admob_ID = ad_reward_diamonds;
    41.                 this.rewardedAdDiamonds = CreateAndLoadRewardedAd(test_adID);// ad_reward_diamonds);
    42.             }
    43.         }
    44. #endif
    45.     }
    46.  
    47.     public RewardedAd CreateAndLoadRewardedAd(string adUnitId)
    48.     {
    49.         RewardedAd rewardedAd = new RewardedAd(adUnitId);
    50.  
    51.         rewardedAd.OnUserEarnedReward += HandleUserEarnedReward;
    52.         rewardedAd.OnAdClosed += HandleRewardedAdClosed;
    53.  
    54.      
    55.        // AdRequest request = new AdRequest.Builder().AddTestDevice(AdRequest.TestDeviceSimulator).AddTestDevice("E2940A62C1B655BA").Build();
    56.        // AdRequest request = new AdRequest.Builder().AddTestDevice("E2940A62C1B655BA").Build();
    57.  
    58.        AdRequest request = new AdRequest.Builder().Build();
    59.      
    60.         rewardedAd.LoadAd(request);
    61.         return rewardedAd;
    62.     }
    63.  
    64.     public void ButtonClick()
    65.     {
    66.      
    67. #if UNITY_ANDROID
    68.      
    69.         if (admob)
    70.         {
    71.             if (GetComponentInParent<FailedPanel>())
    72.             {
    73.                 if (rewardedAdFiled.IsLoaded())
    74.                 {
    75.                     rewardedAdFiled.Show();
    76.                 }
    77.             }
    78.             else
    79.             {
    80.                 if (rewardedAdDiamonds.IsLoaded())
    81.                 {
    82.                     rewardedAdDiamonds.Show();
    83.                 }
    84.             }
    85.         }
    86.  
    87. #endif
    88.     }
    89. #if UNITY_ANDROID
    90.  
    91.  
    92.  
    93.     public void HandleRewardedAdClosed(object sender, EventArgs args)
    94.     {      
    95.  
    96.         if (GetComponentInParent<FailedPanel>())
    97.         {
    98.             this.rewardedAdFiled = CreateAndLoadRewardedAd(test_adID);//ad_reward_filedPanel);
    99.         }
    100.         else
    101.         {
    102.             this.rewardedAdDiamonds = CreateAndLoadRewardedAd(test_adID);//ad_reward_diamonds);
    103.         }
    104.         Debug.Log("HandleRewardedAdClosed");
    105.     }
    106.  
    107.     public void HandleUserEarnedReward(object sender, Reward args)
    108.     {
    109.         string type = args.Type;
    110.         amount = args.Amount;
    111.         ffm.SetDiamonds(ffm.GetDiamonds() + (int)amount);
    112.         Debug.Log("UserEarnedReward");
    113.     }
    114.  
    115. #endif
    116. }
    This code hangs on the button prefab, which is located on two different panels:
     
    Last edited: Mar 13, 2020
  17. marianomdq

    marianomdq

    Joined:
    Sep 29, 2015
    Posts:
    17
    I've been dealing with the get_gameObject can only be called from the main thread error for the past two days.

    It started happening after implementing Async Tasks on one of my systems, so I thought that I was doing something wrong, although the new running threads were not accessing any Unity component.

    Finally, the problem was that Unity was messing something up. I don't know what was it, but after deleting the Library folder and let Unity rebuild everything again, it worked like a charm. The IL2CPP compilation was taking about 30 seconds and after deleting the Library folder it took about 2 minutes, so definitely something was being cached and messing with my Android build.

    The code was the following:

    Code (CSharp):
    1. public class PluginIntegration : MonoBehaviour
    2. {
    3.     private const int PluginTimeoutMilliseconds = 30000;
    4.  
    5.     [SerializeField] private RectTransform _loadingPanel;
    6.     [SerializeField] private RectTransform _takingTooLongWarningPanel;
    7.  
    8.     private Plugin _plugin = null;
    9.     private TaskCompletionSource<object> _launchPluginTimeoutTcs = null;
    10.  
    11.     protected void Awake()
    12.     {
    13.         _plugin = new Plugin();
    14.  
    15.         // Subscribe to the Plugin Ready event
    16.         _plugin.OnPluginReady += PluginReadyHandler;
    17.     }
    18.  
    19.     protected void OnDestroy()
    20.     {
    21.         // Unsubscribe from the Plugin Ready event
    22.         _plugin.OnPluginReady -= PluginReadyHandler;
    23.  
    24.         _plugin = null;
    25.     }
    26.  
    27.  
    28.     // Called from a UI button
    29.     public async void LaunchPlugin()
    30.     {
    31.         if (_launchPluginTimeoutTcs != null)
    32.         {
    33.             Debug.LogWarning("Trying to launch the Plugin while a launching task is already in progress.");
    34.         }
    35.         else
    36.         {
    37.             // Instantiate a new TaskCompletionSource used for asynchronously waiting for a timeout
    38.             _launchPluginTimeoutTcs = new TaskCompletionSource<object>();
    39.             Task timeoutTask = _launchPluginTimeoutTcs.Task;
    40.      
    41.             // Create a cancellation token source with a timeout value
    42.             CancellationTokenSource cts = new CancellationTokenSource(PluginTimeoutMilliseconds);
    43.             var ctsRegistration = cts.Token.Register(() => { _launchPluginTimeoutTcs.SetCanceled(); });
    44.  
    45.             try
    46.             {
    47.                 // Show loading panel.
    48.                 _loadingPanel.gameObject.SetActive(true);
    49.          
    50.                 // Launch the plugin.
    51.                 _plugin.LaunchPlugin();
    52.  
    53.                 // Wait until we receive a ready message, or a timeout happens.
    54.                 await timeoutTask;
    55.             }
    56.             catch (TaskCanceledException)
    57.             {
    58.                 _takingTooLongWarningPanel.gameObject.SetActive(true);
    59.                 Debug.LogWarning("Plugin is taking too long to launch.");
    60.             }
    61.             catch (Exception e)
    62.             {
    63.                 Debug.LogException(e);
    64.             }
    65.             finally
    66.             {
    67.                 // Dispose CancellationTokenSource and CancellationTokenRegistration
    68.                 ctsRegistration.Dispose();
    69.                 cts.Dispose();
    70.          
    71.                 _launchPluginTimeoutTcs = null;
    72.             }
    73.         }
    74.     }
    75.  
    76.     private void PluginReadyHandler()
    77.     {
    78.         // If the completion task source is still available, set it as completed
    79.         _launchPluginTimeoutTcs?.SetResult(null);
    80.  
    81.         // The following two methods were the ones failing with the *get_gameObject can only be called...* exception
    82.         // But, as you can see, the callback was not suppose to be called from a separate thread.
    83.         // The TimeoutTask was only a countdown timer for detecting too much time being elapsed before receiving
    84.         // the "ready" callback.
    85.         // The actual LaunchPlugin method is not being called asynchronously. So, the callback handler
    86.         // is actually being handled on the main thread.
    87.  
    88.         // Hide the Loading spinner
    89.         _loadingPanel.gameObject.SetActive(false);
    90.  
    91.         // Hide the timeout warning panel
    92.         _takingTooLongWarningPanel.gameObject.SetActive(false);
    93.     }
    94. }


    EDIT: This was working fine for iOS Debug and Release builds and for Android Release builds. But for Android Debug builds it was still failing with the "main thread" exception. Most surely related to some underlying OS behavior that handles thread processing priorities differently than it does with the other build types.

    Finally, I had no other choice but to use something like the UnityMainThreadDispatcher script for processing my plugin callbacks on the main thread. Script can be found here.
     
    Last edited: Dec 16, 2020
  18. ritesh_khokhani

    ritesh_khokhani

    Joined:
    Jun 26, 2013
    Posts:
    47
    I think the problem is, after playing rewarded video you can't call unity method before unity switches to main thread again. You need to wait till onApplicationPause called.