Await without Task

Just felt like making this extension to support awaiting handles directly. I figured some people could use this, especially with the Task property not working well in WebGL right now.

Feel free to add this to the library, Addressables devs!

P.S. handle.CompletedTypeless is misleading, I expected it to just let me add an Action without arguments, which would’ve made this extension more efficient.

using System;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Runtime.CompilerServices;

public static class AsyncOperationHandleExtensions
{
    public struct AsyncOperationHandleAwaiter<T> : INotifyCompletion
    {
        private AsyncOperationHandle<T> _handle;

        public AsyncOperationHandleAwaiter(AsyncOperationHandle<T> handle)
        {
            _handle = handle;
        }

        public bool IsCompleted
        {
            get
            {
                return _handle.IsDone;
            }
        }

        public T GetResult()
        {
            if (_handle.Status == AsyncOperationStatus.Succeeded)
            {
                return _handle.Result;
            }
            throw _handle.OperationException;
        }

        public void OnCompleted(Action continuation)
        {
            _handle.Completed += _ => continuation();
        }
    }

    public struct AsyncOperationHandleAwaiter : INotifyCompletion
    {
        private AsyncOperationHandle _handle;

        public AsyncOperationHandleAwaiter(AsyncOperationHandle handle)
        {
            _handle = handle;
        }

        public bool IsCompleted
        {
            get
            {
                return _handle.IsDone;
            }
        }

        public object GetResult()
        {
            if (_handle.Status == AsyncOperationStatus.Succeeded)
            {
                return _handle.Result;
            }
            throw _handle.OperationException;
        }

        public void OnCompleted(Action continuation)
        {
            _handle.Completed += _ => continuation();
        }
    }

    /// <summary>
    /// Used to support the await keyword for AsyncOperationHandle.
    /// </summary>
    public static AsyncOperationHandleAwaiter<T> GetAwaiter<T>(this AsyncOperationHandle<T> handle)
    {
        return new AsyncOperationHandleAwaiter<T>(handle);
    }

    /// <summary>
    /// Used to support the await keyword for AsyncOperationHandle.
    /// </summary>
    public static AsyncOperationHandleAwaiter GetAwaiter(this AsyncOperationHandle handle)
    {
        return new AsyncOperationHandleAwaiter(handle);
    }
}
3 Likes

Then that leads to these simple extensions which can be used to get a Task that will actually work properly in WebGL:

public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
{
    return await handle;
}

public static async Task<object> ToTask(this AsyncOperationHandle handle)
{
    return await handle;
}
2 Likes

what makes this work over the regular asynoperationhandle handle in webgl? isnt this just a wrapper?

These are extensions that let you use the await keyword directly on an asynoperationhandle. Without this, you cannot await the handle without accessing the Task property. But the Task property doesn’t work in WebGL, so the ToTask extensions here let you create a Task that does work in WebGL (so you can use it in Task.WhenAll(), for example).

I gain errors when implementing this script

Assets\AsyncOperationHandleExtensions.cs(87,33): error CS1983: The return type of an async method must be void, Task or Task

Assets\AsyncOperationHandleExtensions.cs(87,25): error CS0308: The non-generic type ‘Task’ cannot be used with type arguments

for each of the functions
both functions are places in the AsyncOperationHandleExtensions folder, Task is still not recognized.
which libraries are in the “using” to solve it?

I’m not sure what your issue is. Are you using the .Net 4.x scripting runtime? async/await do not work in the .Net 3.5 scripting runtime.

sorry but i cant undestand how can i use this script on my project ?

Something like this. Note that async/await requires .Net 4.x scripting runtime.

async void LoadAndInstantiate(string address)
{
    var handle = Addressables.LoadAssetAsync<GameObject>(address);
    var go = await handle;
    Instantiate(go);
}

i gave an error " : ‘AsyncOperationHandle’ does not contain a definition for ‘GetAwaiter’ and no accessible extension method ‘GetAwaiter’ accepting a first argument of type ‘AsyncOperationHandle’ could be found (are you missing a using directive or an assembly reference?) " how can i fix ?

also what are the difference between reference.InstantiateAsync() and Instantiate ?
can I load an object before instantiating? without instantiateAsync?

You need to include the script from my original post in your project. ^^^

InstantiateAsync is an addressables function that keeps track of the internal reference counter so that the memory can be released when the object is destroyed (only works with Addressables.ReleaseInstance). My example there loads the asset without instantiating it, then instantiates it with the synchronous Object.Instantiate, which addressables does not track.

yeah i got it but i dont understand where should i add main script ? which folder ?

It doesn’t matter, you can place it anywhere under Assets. I usually place all my scripts under Asset/Scripts. You might even place it in something like Assets/Scripts/Extensions.

Thank you for this extension. Worked great!

1 Like

Hey, so I placed his first script in my assets in another folder and I named it this AsyncOperationHandleExtensions.
And now I have my original script which was giving me a problem

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class LoadRemote : MonoBehaviour
{
    [SerializeField] private string _label;
    void Start()
    {
        Get(_label);
    }

    private async Task Get(string label)
    {
        var locations = await Addressables.LoadResourceLocationsAsync(label).Task;

        foreach (var location in locations)
            await Addressables.InstantiateAsync(location).Task;
    }

}

And for the second part of the extention OP posted, where do i put that, or do I use one of the methods inplace of my ‘private async’ method?

public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
{
    return await handle;
}
public static async Task<object> ToTask(this AsyncOperationHandle handle)
{
    return await handle;
}

TIA

@dineshbhathad You can place the ToTask extensions in the same AsyncOperationHandleExtensions class.

And if you’re using this extension, you don’t need the .Task when you await them in your example.

var locations = await Addressables.LoadResourceLocationsAsync(label).Task;

Can be simplified to:

var locations = await Addressables.LoadResourceLocationsAsync(label);

But if you need the Task to pass to Task.WhenAll or such, you can use the .ToTask() extension instead of .Task property to work in WebGL.

Hey, thanks for the quick reply. However, I am still pretty new to C# and I am unable to understand how exactly to use your solution.

I placed the below lines into the main extension class and had to added ‘using System.Threading.Tasks;’ to the top of the script.

public static class AsyncOperationHandleExtensions
{
public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
{
    return await handle;
}
public static async Task<object> ToTask(this AsyncOperationHandle handle)
{
    return await handle;

//rest of the scrip
}

Now how do I use this extension into my script below?

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;



public class LoadRemote : MonoBehaviour
{
    [SerializeField] private string _label;
    void Start()
    {
        Get(_label);
    }


    private async Task Get(string label)
    {
        var locations = await Addressables.LoadResourceLocationsAsync(label).Task;

        foreach (var location in locations)
            await Addressables.InstantiateAsync(location).Task;
    }

}

I am really not able to find a suitable tutorial on how to implement extension and the one’s I found are using a different example to show which isn’t helping.

@dineshbhathad Like this:

private async Task Get(string label)
    {
        var locations = await Addressables.LoadResourceLocationsAsync(label);
        foreach (var location in locations)
            await Addressables.InstantiateAsync(location);
    }

Or like this:

private async Task Get(string label)
    {
        var locations = await Addressables.LoadResourceLocationsAsync(label).ToTask();
        foreach (var location in locations)
            await Addressables.InstantiateAsync(location).ToTask();
    }