Search Unity

Any plans for supporting C# async Task and await features?

Discussion in 'Addressables' started by Xarbrough, Jan 13, 2019.

  1. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    503
    I was wondering if the idea of async Task and await for the Addressables API has been considered. Would this be possible or are there drawbacks?

    I would imagine the API to look something like this:

    Code (CSharp):
    1. public AssetReference myReference;
    2.  
    3. async void Start()
    4. {
    5.     GameObject prefab = await myReference.LoadAsset<GameObject>().Result;
    6.     // ...
    7. }
    I do realize that the existing system is very similar to .Net tasks already, but I wonder if it wouldn't be even less user code and potentially less garbage allocations when using the compiler generated tasks instead of using custom events.
     
  2. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    I am using UniRx.Async with this extension method:

    Code (CSharp):
    1.  
    2. using UniRx.Async;
    3.  
    4. ...
    5.  
    6.         public static UniTask<T>.Awaiter GetAwaiter<T>(this IAsyncOperation<T> operation) {
    7.             var tcs = new UniTaskCompletionSource<T>();
    8.             Action<IAsyncOperation<T>> eventHandler = null;
    9.             eventHandler = (res) => {
    10.                 // operation.Completed -= eventHandler; // << somehow this messes up all other listeners, so I commented it out
    11.                 tcs.TrySetResult(res.Result);
    12.             };
    13.             operation.Completed += eventHandler;
    14.             return tcs.Task.GetAwaiter();
    15.         }
    16.  
    17.         public static UniTask.Awaiter GetAwaiter(this IAsyncOperation operation) {
    18.             return operation.ToUniTask().GetAwaiter();
    19.         }
    20.  
    Then you can do:
    Code (CSharp):
    1.  
    2. GameObject go = await myReference.LoadAsset<GameObject>();
    3.  
    4. or
    5. GameObject go = await Addressables.LoadAsset<GameObject>("Assets/MyObject.prefab");
     
  3. unity_bill

    unity_bill

    Unity Technologies

    Joined:
    Apr 11, 2017
    Posts:
    913
    it's coming in the next (1.0) release.

    Of note, we're not going to make the IAsyncOperation actually await-able. Instead it'll have a task property. so you'd await LoadAsset<>().task. This means someone not wanting to await won't extra stuff created/processed. And it also makes it cleaner for someone using .NET 3.x since that won't support tasks.
     
  4. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    476
    Perfect solution, many thanks for this.
     
    unity_bill likes this.
  5. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    461
    actually, you can make IAsyncOperation awaitable through an extension method (that could be defined in another assembly, if you don't need to access private data). there are example implementations on this forum too.
    also, you need to create the Task when you access it to await it. custom awaitable means you can optimize this case (e.g. by linking directly to the completion event in the core system)

    (if you do make the task property, I could still do this though:
    Code (CSharp):
    1. static class AsyncOperationExtensions {
    2.     public static TaskAwaiter GetAwaiter(this IAsyncOperation op) => op.Task.GetAwaiter();
    3. }
    and then
    await LoadAsset<>();
    directly)

    ps: TaskAwaiter is a struct (https://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs) you can make your own AsyncOperationAwaiter struct like this and
    await LoadAsset<>();
    would not have any more allocations than
    LoadAsset<>().Completed += () => {capture locals};
    or yielding it in a coroutine
     
  6. Twyker_gp

    Twyker_gp

    Joined:
    Dec 4, 2018
    Posts:
    9
    Seems like IAsyncOperation is inaccessible in newer versions?
     
  7. chanon81

    chanon81

    Joined:
    Oct 6, 2015
    Posts:
    92
    Here is the updated version:

    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine.Events;
    4. using UnityEngine.ResourceManagement.AsyncOperations;
    5. using UniRx.Async;
    6.  
    7. namespace VQ {
    8.     public static class VQExtAsync {
    9.         public static UniTask<T>.Awaiter GetAwaiter<T>(this AsyncOperationHandle<T> operation) {
    10.             var tcs = new UniTaskCompletionSource<T>();
    11.             Action<AsyncOperationHandle<T>> eventHandler = null;
    12.             eventHandler = (res) => {
    13.                 // operation.Completed -= eventHandler; // we can't seem to do this!?
    14.                 tcs.TrySetResult(res.Result);
    15.             };
    16.             operation.Completed += eventHandler;
    17.             return tcs.Task.GetAwaiter();
    18.         }
    19.  
    20.         public static UniTask.Awaiter GetAwaiter(this AsyncOperationHandle operation) {
    21.             return operation.ToUniTask().GetAwaiter();
    22.         }
    23.  
    24.         // bonus .. you can await UnitytEvents
    25.         public static UniTask.Awaiter GetAwaiter(this UnityEvent uevent) {
    26.             var tcs = new UniTaskCompletionSource();
    27.             UnityAction eventHandler = null;
    28.             eventHandler = () => {
    29.                 uevent.RemoveListener(eventHandler);
    30.                 tcs.TrySetResult();
    31.             };
    32.             uevent.AddListener(eventHandler);
    33.             return tcs.Task.GetAwaiter();
    34.         }
    35.     }
    36. }
    37.  
     
    Twyker_gp likes this.