Best way to cancel/abort/stop an async load operation?

Hey everyone, I’m currently migrating our project from “Resources” to Addressables.
The worst pain so far is handling the asynchronous loading in a code base that is used to loading everything synchronously.

My question is, what is the best way to load an asset that could end up being invalidated before the loading is complete.

Consider this code:

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class BasicReference : MonoBehaviour
{
    public AssetReference baseCube;

    private GameObject _object = null;

    public void SpawnThing()
    {
        this.baseCube.LoadAssetAsync<GameObject>().Completed += OnLoaded;
    }

    public void DestroyThing()
    {
        if (this._object != null)
        {
            Addressables.Release(this._object);
        }
    }

    public void OnLoaded(AsyncOperationHandle<GameObject> operationHandle)
    {
        Debug.Assert(operationHandle.Status == AsyncOperationStatus.Succeeded);
        this._object = operationHandle.Result;
        // Do stuff with _object.
    }
}

If SpawnThing() is called, then DestroyThing() is called before the load completion call OnLoaded(), we will end up with an asset reference leak and a wrong this._object. And we wouldn’t want that!

What would be the best way to prevent this situation?
For context, we are on PC without remote bundles, so if it comes to a dirty synchronous way of doing this I would consider it (as the current sync code works fine as it is).

1 Like

Our team ran into this recently and basically just at the top of OnLoaded (or in the Coroutine in our case) we check if OnDestroy has been called or if the gameObject is null. If so we Release the asset immediately.

One other difference in what we do is hold the AsyncOperationHandle and check IsValid() on the handle before trying to Release. That seemed to fix some issues in cases where we had destroyed things that may not be loaded.

I’m not sure if this is the best (or even super viable) way long term so I’m definitely interested in hearing others’ thoughts.

Edit: Just realized our use case is a little different. In ours, the MonoBehavior that’s calling LoadAssetAsync is destroyed before the load is finished. But it requires a similar need to have a way to cancel that LoadAssetAsync.

I ran some tests, seems like if you call unload first, the gameobject contained in operation handle is null when OnLoaded() is called. However I think it’s better if the OnLoaded() is never called if unloaded.