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
  2. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  3. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

SynchronizationContext.Send to main thread fails?

Discussion in 'Experimental Scripting Previews' started by vs-cby, Jul 13, 2017.

  1. vs-cby

    vs-cby

    Joined:
    Jun 23, 2017
    Posts:
    5
    I was using the SynchronizationContext.Current to send a function from a background thread to the main thread e.g. to create a gameobject.
    See this code sample:

    Code (CSharp):
    1.         // Context from main thread:
    2.         var syncContext = SynchronizationContext.Current;
    3.  
    4.         // runs on main thread - works:
    5.         var sphere0 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    6.  
    7.         // create GameObject from background task:
    8.         await Task.Run(
    9.             () =>
    10.                 {
    11.                     syncContext.Post(
    12.                         s =>
    13.                             {
    14.                                 // works:
    15.                                 var sphere1 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    16.                             },
    17.                         null);
    18.                 });
    19.  
    20.         await Task.Run(
    21.             () =>
    22.                 {
    23.                     syncContext.Send(
    24.                         s =>
    25.                             {
    26.                                 // does NOT work:
    27.                                 var sphere2 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    28.                             },
    29.                         null);
    30.                 });

    The Post method (fire-and-forget) works, but the Send fails with

    CreatePrimitive can only be called from the main thread.
    Constructors and field initializers will be executed from the loading thread when loading a scene.
    Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
    UnityEngine.GameObject:CreatePrimitive(PrimitiveType)

    How come?
    Thanks for any help.
     
  2. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Send() is implemented as
    Code (CSharp):
    1. public override void Send(SendOrPostCallback callback, object state)
    2. {
    3.     callback(state);
    4. }
    It feels useless. Throwing NotImplementedException or NotSupportedException would be more fair.

    I think, the proper implementation should look more like this:
    Code (CSharp):
    1. public override void Send(SendOrPostCallback callback, object state)
    2. {
    3.     if (SynchronizationContext.Current == this)
    4.     {
    5.         callback(state);
    6.     }
    7.     else
    8.     {
    9.         var waitHandle = new ManualResetEvent(false);
    10.  
    11.         lock (m_AsyncWorkQueue)
    12.         {
    13.             m_AsyncWorkQueue.Enqueue(new WorkRequest(callback, state));
    14.             m_AsyncWorkQueue.Enqueue(new WorkRequest(_=> waitHandle.Set(), null));
    15.         }
    16.  
    17.         waitHandle.WaitOne();
    18.     }
    19. }
     
    bddckr likes this.
  3. j-borden

    j-borden

    Joined:
    Mar 3, 2015
    Posts:
    21
    I'm surprised the Post even works in this case to be honest! Why would

    <code>
    ThreadPool.QueueUserWorkItem (new WaitCallback (d), state);
    </code>

    put you back on the main thread?
     
  4. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    SynchronizationContext.Post is expected to execute the delegate within the corresponding context.
    ThreadPool.QueueUserWorkItem schedules the execution in one of the thread pool's threads.
     
  5. j-borden

    j-borden

    Joined:
    Mar 3, 2015
    Posts:
    21
    Right, so I guess the above code just got lucky? The SynchronizationContext "Post" method just queues the action onto one of the threadpool threads. Is the main thread one of those? I don't understand why that would work like OP claims.
     
  6. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    This is what the base implementation of SynchronizationContext does. Unity has the custom UnitySynchronizationContext, which is assigned to the main thread and schedules the jobs to this thread just like WindowsFormsSynchronizationContext or DispatcherSynchronizationContext do.
     
  7. j-borden

    j-borden

    Joined:
    Mar 3, 2015
    Posts:
    21
    Oh ok that makes sense then, and it's good to know that it is there!
     
  8. vs-cby

    vs-cby

    Joined:
    Jun 23, 2017
    Posts:
    5
    Thanks for all reactions.

    Anyone around from Unity, to confirm the Send() method of UnitySynchronizationContext will be fixed?
     
  9. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    We're looking into it now. Can you submit a bug report so that we can properly track this issue?
     
  10. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22