Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice
  2. Ever participated in one our Game Jams? Want pointers on your project? Our Evangelists will be available on Friday to give feedback. Come share your games with us!
    Dismiss Notice

Async/Await support for loading assets?

Discussion in 'Addressables' started by james7132, Jul 3, 2018.

  1. james7132


    Mar 6, 2015
    With the .NET 4.6 runtime out of experimental status with active support for async/await, is adding async APIs on the roadmap anywhere? It makes a lot more sense to be using it than IAsyncOperation. Both will generate about the same amount of GC allocated garbage, the use case doesn't generate constant garbage, and async/await proactively avoids calback hell.

    There is the option of writing an extension method to convert IAsyncOperations into Tasks, which I've previously done with the older AsyncOperation, but honestly would rather have this supported in first party APIs rather than skirting around the issue.

    As for not breaking support for .NET 3.5 projects, perhaps put these in conditional compilation blocks?
    M_R likes this.
  2. rigidbuddy


    Feb 25, 2014
    Actually you could already make IAsyncOperation awaitable to use it in async methods. And you shouldn't convert it to Task
    Code (CSharp):
    1.  public struct AsyncOperationAwaiter : INotifyCompletion
    2.     {
    3.         private readonly IAsyncOperation _asyncOperation;
    5.         public AsyncOperationAwaiter(IAsyncOperation asyncOperation)
    6.         {
    7.             _asyncOperation = asyncOperation;
    8.         }
    10.         public AsyncOperationAwaiter GetAwaiter() => this;
    12.         public void GetResult()
    13.         {
    14.         }
    16.         public bool IsCompleted => _asyncOperation.IsDone;
    18.         public void OnCompleted(Action action)
    19.         {
    20.             action.Invoke();
    21.         }
    23.         private async Task GetValue(Action action)
    24.         {
    25.             while (!_asyncOperation.IsDone)
    26.                 await Task.Yield();
    28.             action();
    29.         }
    30.     }
    And make extension method named GetAwaiter

    Code (CSharp):
    1.    public static class AwaitExtensions
    2.     {
    3.         public static AsyncOperationAwaiter GetAwaiter(this IAsyncOperation asyncOp) => new AsyncOperationAwaiter(asyncOp);
    4.     }
    After it IAsyncOperation "magically" gets awaitable and you could compile expressions like
    await prefab.Instantiate() without errors.

    Hope it'll help.

    PS: Actually that implementation of AsyncOperationAwaiter.GetValue is bad: yielding is bad a practice and Task could be replaced by ValueTask.

    PPS: After awaiting it you could end up on non-main thread. Check it using:

    Code (CSharp):
    1.         public static bool IsMainThread() => Thread.CurrentThread.ManagedThreadId == 1;
    If so you could write custom TaskScheduler and SynchronizationContext which could return your task continuations back to the main thread
    Last edited: Jul 4, 2018
    DaniParra likes this.
  3. james7132


    Mar 6, 2015
    Thanks for the tip! That'll definitely clean up the amount extra "ToTask" chained calls I have littered around.

    Using the .NET 4.x Runtime with Async/Await + Tasks introduces a UnitySynchronizationContext when called from the main thread, which makes subsequent continuations occur on the main thread. I'm not sure if this is supported in ValueTasks, other custom awaitables, or if a public API that be called to fetch this context currently exists.
  4. rigidbuddy


    Feb 25, 2014
    Btw, you could implement your own custom async task type and use it with await based on continuations and/or your own schedulers - it's very handy.

    For example: I've implemented custom lightweight task type which:
    1) could be stopped and disposed immediately with child tasks
    2) in debug mode it shows file, method name and line numbers on current await for every active task
    3) without any threading, only continuation passing

    In short: you should make a builder class (see AsyncMethodBuilder) which provides callbacks which are injected by compiler in generated state machine after/before awaits and so on.
  5. Elhimp


    Jan 6, 2013