Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only.

    Please, do not make any changes to your username or email addresses at id.unity.com during this transition time.

    It's still possible to reply to existing private message conversations during the migration, but any new replies you post will be missing after the main migration is complete. We'll do our best to migrate these messages in a follow-up step.

    On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live.


    Read our full announcement for more information and let us know if you have any questions.

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,938
    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