Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Await without Task

Discussion in 'Addressables' started by ProtoTerminator, Aug 14, 2020.

  1. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    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.

    Code (CSharp):
    1. using System;
    2. using UnityEngine.ResourceManagement.AsyncOperations;
    3. using System.Runtime.CompilerServices;
    4.  
    5. public static class AsyncOperationHandleExtensions
    6. {
    7.     public struct AsyncOperationHandleAwaiter<T> : INotifyCompletion
    8.     {
    9.         private AsyncOperationHandle<T> _handle;
    10.  
    11.         public AsyncOperationHandleAwaiter(AsyncOperationHandle<T> handle)
    12.         {
    13.             _handle = handle;
    14.         }
    15.  
    16.         public bool IsCompleted
    17.         {
    18.             get
    19.             {
    20.                 return _handle.IsDone;
    21.             }
    22.         }
    23.  
    24.         public T GetResult()
    25.         {
    26.             if (_handle.Status == AsyncOperationStatus.Succeeded)
    27.             {
    28.                 return _handle.Result;
    29.             }
    30.             throw _handle.OperationException;
    31.         }
    32.  
    33.         public void OnCompleted(Action continuation)
    34.         {
    35.             _handle.Completed += _ => continuation();
    36.         }
    37.     }
    38.  
    39.     public struct AsyncOperationHandleAwaiter : INotifyCompletion
    40.     {
    41.         private AsyncOperationHandle _handle;
    42.  
    43.         public AsyncOperationHandleAwaiter(AsyncOperationHandle handle)
    44.         {
    45.             _handle = handle;
    46.         }
    47.  
    48.         public bool IsCompleted
    49.         {
    50.             get
    51.             {
    52.                 return _handle.IsDone;
    53.             }
    54.         }
    55.  
    56.         public object GetResult()
    57.         {
    58.             if (_handle.Status == AsyncOperationStatus.Succeeded)
    59.             {
    60.                 return _handle.Result;
    61.             }
    62.             throw _handle.OperationException;
    63.         }
    64.  
    65.         public void OnCompleted(Action continuation)
    66.         {
    67.             _handle.Completed += _ => continuation();
    68.         }
    69.     }
    70.  
    71.     /// <summary>
    72.     /// Used to support the await keyword for AsyncOperationHandle.
    73.     /// </summary>
    74.     public static AsyncOperationHandleAwaiter<T> GetAwaiter<T>(this AsyncOperationHandle<T> handle)
    75.     {
    76.         return new AsyncOperationHandleAwaiter<T>(handle);
    77.     }
    78.  
    79.     /// <summary>
    80.     /// Used to support the await keyword for AsyncOperationHandle.
    81.     /// </summary>
    82.     public static AsyncOperationHandleAwaiter GetAwaiter(this AsyncOperationHandle handle)
    83.     {
    84.         return new AsyncOperationHandleAwaiter(handle);
    85.     }
    86. }
     
    samanabo, xeniaosense and LucasHehir like this.
  2. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    Then that leads to these simple extensions which can be used to get a Task that will actually work properly in WebGL:


    Code (CSharp):
    1. public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
    2. {
    3.     return await handle;
    4. }
    5.  
    6. public static async Task<object> ToTask(this AsyncOperationHandle handle)
    7. {
    8.     return await handle;
    9. }
     
    _watcher_ and LucasHehir like this.
  3. realcosmik

    realcosmik

    Joined:
    Nov 27, 2018
    Posts:
    20
    what makes this work over the regular asynoperationhandle handle in webgl? isnt this just a wrapper?
     
  4. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    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).
     
  5. Nadoc_NewLedge

    Nadoc_NewLedge

    Joined:
    Nov 26, 2020
    Posts:
    19
    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<T>

    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?
     
  6. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    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.
     
  7. ugurberkecanunlu

    ugurberkecanunlu

    Joined:
    Dec 20, 2019
    Posts:
    7
    sorry but i cant undestand how can i use this script on my project ?
     
  8. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    Something like this. Note that async/await requires .Net 4.x scripting runtime.

    Code (CSharp):
    1. async void LoadAndInstantiate(string address)
    2. {
    3.     var handle = Addressables.LoadAssetAsync<GameObject>(address);
    4.     var go = await handle;
    5.     Instantiate(go);
    6. }
     
  9. ugurberkecanunlu

    ugurberkecanunlu

    Joined:
    Dec 20, 2019
    Posts:
    7
    i gave an error " : 'AsyncOperationHandle<GameObject>' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'AsyncOperationHandle<GameObject>' 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?
     
    Last edited: Jan 24, 2021
  10. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    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.
     
  11. ugurberkecanunlu

    ugurberkecanunlu

    Joined:
    Dec 20, 2019
    Posts:
    7
    yeah i got it but i dont understand where should i add main script ? which folder ?
     
  12. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    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.
     
  13. GSanLob

    GSanLob

    Joined:
    Oct 11, 2017
    Posts:
    14
    Thank you for this extension. Worked great!
     
    ProtoTerminator likes this.
  14. dineshbhathad

    dineshbhathad

    Joined:
    Jan 12, 2022
    Posts:
    5
    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
    Code (CSharp):
    1. using System.Threading.Tasks;
    2. using UnityEngine;
    3. using UnityEngine.AddressableAssets;
    4.  
    5. public class LoadRemote : MonoBehaviour
    6. {
    7.     [SerializeField] private string _label;
    8.     void Start()
    9.     {
    10.         Get(_label);
    11.     }
    12.  
    13.     private async Task Get(string label)
    14.     {
    15.         var locations = await Addressables.LoadResourceLocationsAsync(label).Task;
    16.  
    17.         foreach (var location in locations)
    18.             await Addressables.InstantiateAsync(location).Task;
    19.     }
    20.  
    21. }
    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?

    Code (CSharp):
    1. public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
    2. {
    3.     return await handle;
    4. }
    5. public static async Task<object> ToTask(this AsyncOperationHandle handle)
    6. {
    7.     return await handle;
    8. }
    TIA
     
  15. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    @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.
    Code (CSharp):
    1. var locations = await Addressables.LoadResourceLocationsAsync(label).Task;
    Can be simplified to:
    Code (CSharp):
    1. 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.
     
    Last edited: May 26, 2022
  16. dineshbhathad

    dineshbhathad

    Joined:
    Jan 12, 2022
    Posts:
    5
    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.

    Code (CSharp):
    1.  
    2. public static class AsyncOperationHandleExtensions
    3. {
    4. public static async Task<T> ToTask<T>(this AsyncOperationHandle<T> handle)
    5. {
    6.     return await handle;
    7. }
    8. public static async Task<object> ToTask(this AsyncOperationHandle handle)
    9. {
    10.     return await handle;
    11.  
    12. //rest of the scrip
    13. }
    Now how do I use this extension into my script below?
    Code (CSharp):
    1. using System.Threading.Tasks;
    2. using UnityEngine;
    3. using UnityEngine.AddressableAssets;
    4.  
    5.  
    6.  
    7. public class LoadRemote : MonoBehaviour
    8. {
    9.     [SerializeField] private string _label;
    10.     void Start()
    11.     {
    12.         Get(_label);
    13.     }
    14.  
    15.  
    16.     private async Task Get(string label)
    17.     {
    18.         var locations = await Addressables.LoadResourceLocationsAsync(label).Task;
    19.  
    20.         foreach (var location in locations)
    21.             await Addressables.InstantiateAsync(location).Task;
    22.     }
    23.  
    24. }
    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.
     
  17. ProtoTerminator

    ProtoTerminator

    Joined:
    Nov 19, 2013
    Posts:
    581
    @dineshbhathad Like this:
    Code (CSharp):
    1. private async Task Get(string label)
    2.     {
    3.         var locations = await Addressables.LoadResourceLocationsAsync(label);
    4.         foreach (var location in locations)
    5.             await Addressables.InstantiateAsync(location);
    6.     }
    Or like this:
    Code (CSharp):
    1. private async Task Get(string label)
    2.     {
    3.         var locations = await Addressables.LoadResourceLocationsAsync(label).ToTask();
    4.         foreach (var location in locations)
    5.             await Addressables.InstantiateAsync(location).ToTask();
    6.     }