Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Continuations and synchronization context

Discussion in 'Experimental Scripting Previews' started by snacktime, Jan 29, 2018.

  1. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Trying to figure out the behavior I'm seeing with continuations.

    My test case uses a simple pattern of start a task via Task.Run in Update, then continue it using TaskScheduler.FromCurrentSynchronizationContext() as the schedular.

    Even in cases where the task would be completed before Update is completed, the continuation always runs in FixedUpdate.ScriptRunDelayedTasks.

    Which raises the question of when are continuations actually run. Not knowing how the whole event loop is coded I can imagine a couple of scenarios ranging from just yielding in specific places to actually explicitly intercepting continuations and then running them at a set place.

    In any case depending on the behavior determines whether it's a even a useful pattern, IE doing work in another thread and then forcing the continuation to run on the main thread and handle the result there. Versus say just using concurrent queues and checking for done work in say Update.
     
  2. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22
    The current behavior is that the continuation will occur on the main thread after WaitForFixedUpdate in the player loop, but this may change in the future. We're still working on how best to support async and await within the Unity engine. For now, we still recommend using Coroutines.
     
  3. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The behavior I saw was continuations were following the expected behavior and did not run on the main thread by default, unless told to via calling it with TaskScheduler.FromCurrentSynchronizationContext().
     
  4. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22
    We implement our own synchronization context to resume on the main thread (since calling unity apis is not supported on background threads, and they can be invoked via async await keywords in modern C#) so what you're describing makes sense to me. If you have more questions feel free to ask, and let us know if you see any issues so we can get them fixed :)
     
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    By default (no scheduler specified) continuation tasks run on TaskScheduler.Default that is associated with the thread pool.

    I've checked, this code in Unity behaves the same way it does in a WinForms application:
    Code (CSharp):
    1. private void Start()
    2. {
    3.     Print(0); // main thread
    4.  
    5.     var task1 = Task.Run(() => Print(1)); // other thread
    6.     var task2 = task1.ContinueWith(_ => Print(2)); // other thread
    7.     var task3 = task2.ContinueWith(_ => Print(3), TaskScheduler.FromCurrentSynchronizationContext()); // main thread
    8.     var task4 = task3.ContinueWith(_ => Print(4)); // other thread
    9. }
    10.  
    11. private void Print(int n) => Debug.Log(n + ": " + Thread.CurrentThread.ManagedThreadId);
     
    kou-yeung and SugoiDev like this.
  6. StephenHodgson-Valorem

    StephenHodgson-Valorem

    Joined:
    Mar 8, 2017
    Posts:
    148
    Seems Unity uses a lock() a delegate/event is invoked which can result in more nightmare bug hunts so it needs to be addressed.

    It looks like the lock() is just used for having thread-safe collection access. Use the appropriate System.Collections.Concurrent collection (Queue?) type instead of a lock() statement while still having thread-safe collections.

    If exclusive access is required, they might be able to use InterlockedExchange with a Boolean flag that is checked and set thread-safe with the Interlocked helper methods.


    Continuations are posted to the Unity synchronization context. This shouldn't be a problem, but UnitySynchronizationContext has a major problem in its implementation: it locks the message queue while it's executing messages. This means that if we await our continuations, then lock() waiting for some other thread, and that other thread also awaits the continuation, it'll deadlock because the event queue is locked while the sync context is executing.

    Code (CSharp):
    1.  
    2.        private void Exec()
    3.        {
    4.            lock (m_AsyncWorkQueue)
    5.            {
    6.                var workCount = m_AsyncWorkQueue.Count;
    7.                for (int i = 0; i < workCount; i++)
    8.                {
    9.                    var work = m_AsyncWorkQueue.Dequeue();
    10.                    work.Invoke();
    11.                }
    12.            }
    13.        }
    14.  
    See the problem there? Rule of thumb: Never call external events or delegates inside of a lock()
     
    Last edited: Sep 24, 2018
  7. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22
    Please file a bug.
     
  8. StephenHodgson-Valorem

    StephenHodgson-Valorem

    Joined:
    Mar 8, 2017
    Posts:
    148
  9. StephenHodgson-Valorem

    StephenHodgson-Valorem

    Joined:
    Mar 8, 2017
    Posts:
    148
  10. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    43
    Hi there, has the unity support async/await well now? and I wander where dose the continuation occured then,as you have had saied ’may change it‘ .and does Coroutines still recommended rather then async/await now ?
    and sorry for my chinish,haha~
     
    pbrisebois likes this.
  11. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    43
    I think UniTask did good job.