Search Unity

Feedback AsyncOperationBase.Task implementation

Discussion in 'Addressables' started by VolodymyrBS, Feb 20, 2021.

  1. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
    Hi! current AsyncOperationBase.Task property implementation use WaitHandler and start completely new thread just to wait on it. This implementation is far not optimal because of wasting thread from thread pool for blocking wait.
    Current implementation:
    Code (CSharp):
    1.  
    2.         System.Threading.EventWaitHandle m_waitHandle;
    3.         internal System.Threading.WaitHandle WaitHandle
    4.         {
    5.             get
    6.             {
    7.                 if (m_waitHandle == null)
    8.                     m_waitHandle = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);
    9.                 m_waitHandle.Reset();
    10.                 return m_waitHandle;
    11.             }
    12.         }
    13.         internal System.Threading.Tasks.Task<TObject> Task
    14.         {
    15.             get
    16.             {
    17. #if UNITY_WEBGL
    18.                 Debug.LogError("Multithreaded operation are not supported on WebGL.  Unable to aquire Task.");
    19.                 return default;
    20. #else
    21.                 if (Status == AsyncOperationStatus.Failed)
    22.                 {
    23.                     return System.Threading.Tasks.Task.FromResult(default(TObject));
    24.                 }
    25.                 if (Status == AsyncOperationStatus.Succeeded)
    26.                 {
    27.                     return System.Threading.Tasks.Task.FromResult(Result);
    28.                 }
    29.  
    30.                 var handle = WaitHandle;
    31.                 return System.Threading.Tasks.Task.Factory.StartNew((Func<object, TObject>)(o =>
    32.                 {
    33.                     var asyncOperation = o as AsyncOperationBase<TObject>;
    34.                     if (asyncOperation == null)
    35.                         return default(TObject);
    36.                     handle.WaitOne();
    37.                     return (TObject)asyncOperation.Result;
    38.                 }), this);
    39. #endif
    40.             }
    41.  
    42.        
    43.         internal void InvokeCompletionEvent()
    44.         {
    45.             if (m_CompletedActionT != null)
    46.             {
    47.                 m_CompletedActionT.Invoke(new AsyncOperationHandle<TObject>(this));
    48.                 m_CompletedActionT.Clear();
    49.             }
    50.  
    51.             if (m_waitHandle != null)
    52.                 m_waitHandle.Set();
    53.  
    54.             m_InDeferredCallbackQueue = false;
    55.         }
    56.         }
    What do you think about changing implementation to use TaskComletionSource? With it code above could be rewrite to something like this
    Code (CSharp):
    1.         System.Threading.Tasks.TaskCompletionSource<TObject> m_taskCompletionSource;
    2.         internal System.Threading.Tasks.TaskCompletionSource<TObject> TaskCompletionSource
    3.         {
    4.             get
    5.             {
    6.                 if (m_taskCompletionSource == null)
    7.                     m_taskCompletionSource = new System.Threading.Tasks.TaskCompletionSource<TObject>();
    8.                
    9.                 return m_taskCompletionSource;
    10.             }
    11.         }
    12.  
    13.         internal System.Threading.Tasks.Task<TObject> Task
    14.         {
    15.             get
    16.             {
    17.                 if (Status == AsyncOperationStatus.Failed)
    18.                 {
    19.                     return System.Threading.Tasks.Task.FromResult(default(TObject));
    20.                 }
    21.                 if (Status == AsyncOperationStatus.Succeeded)
    22.                 {
    23.                     return System.Threading.Tasks.Task.FromResult(Result);
    24.                 }
    25.  
    26.                 return TaskCompletionSource.Task;
    27.             }
    28.         }
    29.  
    30.         internal void InvokeCompletionEvent()
    31.         {
    32.             if (m_CompletedActionT != null)
    33.             {
    34.                 m_CompletedActionT.Invoke(new AsyncOperationHandle<TObject>(this));
    35.                 m_CompletedActionT.Clear();
    36.             }
    37.  
    38.             if (m_taskCompletionSource != null)
    39.                 m_taskCompletionSource.SetResult(Result);
    40.  
    41.             m_InDeferredCallbackQueue = false;
    42.         }
    Pros: no threads wasting, reusing task instance, work on WebGL(no additional threads involved)
    Cons: personally don't see any
     
    Kirsche and DrViJ like this.
  2. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
  3. Arvtesh

    Arvtesh

    Joined:
    Aug 16, 2014
    Posts:
    94
    This is a problem indeed. This is one of the reasons we have to avoid using Tasks with addressables in our project. Another issue is no support for cancellation tokens.
     
  4. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,822
    I'll flag with the team for some guidance!
     
    VolodymyrBS likes this.
  5. davidla_unity

    davidla_unity

    Unity Technologies

    Joined:
    Nov 17, 2016
    Posts:
    763
    Hm, good idea. I'll make a ticket for us to evaluate this and see what we can do. Thanks for the solution suggestion, I really appreciate it. This is definitely worth investigating.
     
    VolodymyrBS likes this.
  6. Obay_naeem

    Obay_naeem

    Joined:
    Jan 22, 2020
    Posts:
    7
    +1
    can you please add something to cancel or abort while downloading the addressables remotely .