Search Unity

A lot of errors when object with awaited Task was destroyed

Discussion in 'Addressables' started by androshchuk-vladyslav, Oct 15, 2020.

  1. androshchuk-vladyslav

    androshchuk-vladyslav

    Joined:
    Dec 13, 2015
    Posts:
    127
    Hi dear team!

    We are using Addressables a lot in production project. By collecting errors, we discovered a huge amount of System.NullReferenceException: Object reference not set to an instance of an object exceptions.

    Research leaded us to simple fact. Assuming we have Scene 1 with some script, which perform loading in of some object from server in Start:

    Code (CSharp):
    1.  
    2. private async void Start()
    3. {
    4.     AsyncOperationHandle<GameObject> handle = Addressables.InstantiateAsync("address");
    5.     await handle.Task;
    6.  
    7.     if (handle.Status == AsyncOperationStatus.Failed)
    8.         return;
    9.  
    10.     handle.Result.SetActive(false);
    11.     OtherObjectReference.SetActive(false);
    12. }
    13.  
    If providing of resource takes some time on slow internet, user gan easelly navigate to Scene 2 during handle awaiting. But execution continues, and when load was finished handle.Result or OtherObjectReference is null.
    Await a handle's Task have no cancelation token or some canceled status.

    May we have some good solution here?

    Thanks!
     
  2. einWikinger

    einWikinger

    Joined:
    Jul 30, 2013
    Posts:
    97
    As a workaround you can just provide a cancellation token yourself to the Start() state machine and just unload/release the resource handle when you detect cancellation after InstantiateAsync(). If you know it can be interrupted then you should maybe split it up into LoadAssetAsync() and then just a synchronous Instantiate() call. That way, you can better track at which point you are and you can either be interrupted after the asset has loaded (but not instantiated), so you can just release as if nothing has happened or you continue to instantiate it and the rest of your lifetime management logic will take care of dismissing the instance and loaded asset.

    You may also just call Addressables.Release() with the operation handle in case your task was cancelled. It should clean up everything that was "loaded" by the operation (may it be the instance and/or just the prefab).

    The operation should then be cancelled when you notice that the object will go out of scope (i.e. in your case when the other scene is loaded). UniTask provides good wrappers for that kind of boilerplate to e.g. tie the runtime of a state machine to the lifetime of a monobehaviour, just like Unity does automatically with coroutines.
     
  3. androshchuk-vladyslav

    androshchuk-vladyslav

    Joined:
    Dec 13, 2015
    Posts:
    127
    Yes, but all of this are workarounds. I really want a Unity Team to provide one standardized approach to this problem. We have huge amount of loading points, and it should be as simple as UniTask with default cancelation token for game object.
     
  4. einWikinger

    einWikinger

    Joined:
    Jul 30, 2013
    Posts:
    97
    The standardized approach would be to Release() the handle when you don't need it anymore. If you have an object where its lifetime is bound to, you can easily call it in OnDisable or OnDestroy (however your lifetime is bound). Accounting for special use cases would bloat the API and you'd be better off encapsulating this functionality into a separate "add-on" library. If you look at the UniTask implementation of tying cancellation tokens to object lifetimes you see that it's a very convoluted case with a lot of overhead that can be easily reduced for your use case, for example.