Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Async/Await support for loading assets?

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

  1. james7132

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    110
    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

    rigidbuddy

    Joined:
    Feb 25, 2014
    Posts:
    34
    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;
    4.  
    5.         public AsyncOperationAwaiter(IAsyncOperation asyncOperation)
    6.         {
    7.             _asyncOperation = asyncOperation;
    8.         }
    9.  
    10.         public AsyncOperationAwaiter GetAwaiter() => this;
    11.  
    12.         public void GetResult()
    13.         {
    14.         }
    15.  
    16.         public bool IsCompleted => _asyncOperation.IsDone;
    17.  
    18.         public void OnCompleted(Action action)
    19.         {
    20.             action.Invoke();
    21.         }
    22.  
    23.         private async Task GetValue(Action action)
    24.         {
    25.             while (!_asyncOperation.IsDone)
    26.                 await Task.Yield();
    27.  
    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;
    2.  
    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

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    110
    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

    rigidbuddy

    Joined:
    Feb 25, 2014
    Posts:
    34
    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

    Elhimp

    Joined:
    Jan 6, 2013
    Posts:
    20