Search Unity

Feedback Addressables should not require multithreading for Task support

Discussion in 'Addressables' started by leonardo-scur-dot, Nov 6, 2019.

Thread Status:
Not open for further replies.
  1. leonardo-scur-dot

    leonardo-scur-dot

    Joined:
    Nov 20, 2018
    Posts:
    3
    I'm using Addressables on a WebGL title, and am otherwise successfully using managed Tasks for the whole game.

    However, I have stumbled on a issue, that awaiting the tasks returned by IAsyncOperation<T>.Task, yields an error:
    Unrelated, but the correct spelling is "acquire"

    I am a curious person, so I dived into the source code for the Addressables package, and stumbled onto the Task property's getter implementation in AsyncOperationBase. What it currently does is create a Task with the System.Threading.Tasks.Task.Factory.StartNew method, which delegates the provided callback to a shared thread pool. That callback simply blocks on the internal WaitHandle, then returns the result.

    I am not privy to all implementation details of the Addressables system, but from reading that method, there is no point for having this multithreading requirement, as one could use the Completed/Destroyed events together with a TaskCompletionSource instead, which would allow all of this to happen in a single thread.

    In fact, I have implemented this for my own use as an extension method and it works, but I think that Unity would benefit on having this implementation for the whole userbase. Below is the snippet:
    Code (CSharp):
    1. public static Task<T> Task<T>(this AsyncOperationHandle<T> self)
    2. {
    3.     var source = new TaskCompletionSource<T>();
    4.  
    5.     void Cleanup()
    6.     {
    7.         self.Completed -= OnCompleted;
    8.         self.Destroyed -= OnDestroyed;
    9.     }
    10.  
    11.     void OnCompleted(AsyncOperationHandle<T> handle)
    12.     {
    13.         Cleanup();
    14.  
    15.         switch (handle.Status)
    16.         {
    17.             case AsyncOperationStatus.Failed:
    18.                 source.SetException(handle.OperationException);
    19.                 break;
    20.             case AsyncOperationStatus.Succeeded:
    21.                 source.SetResult(handle.Result);
    22.                 break;
    23.         }
    24.     }
    25.  
    26.     void OnDestroyed(AsyncOperationHandle handle)
    27.     {
    28.         Cleanup();
    29.         source.SetCanceled();
    30.     }
    31.  
    32.     self.Completed += OnCompleted;
    33.     self.Destroyed += OnDestroyed;
    34.  
    35.     return source.Task;
    36. }
     
    Last edited: Nov 6, 2019
    pjbaron, firaui and vitorfgd like this.
  2. davidla_unity

    davidla_unity

    Unity Technologies

    Joined:
    Nov 17, 2016
    Posts:
    763
    Just wanted to add to this that we actually have a task to look into adding this in the future. I can't make any promises as to if or when but I just wanted to let you know we do have a task to look into it.
     
    brunocoimbra likes this.
  3. leonardo-scur-dot

    leonardo-scur-dot

    Joined:
    Nov 20, 2018
    Posts:
    3
    In fact, the current implementation is dangerous even in non-WebGL platforms that do support multithreading, because loading multiple assets and accessing each operation's Task will potentially deplete the managed ThreadPool, if the number of tasks is equal or greater to the max thread count on the pool. That will asynchronously delay whatever other parts of the engine, third-party libraries or user code that use the threadpool until all operations succeed, and since they are IO operations primarily, there shouldn't be a need to block those threads.
     
Thread Status:
Not open for further replies.