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

Mono Upgrade SynchronizationContext for main thread

Discussion in 'Experimental Scripting Previews' started by Goldseeker, Sep 29, 2016.

  1. Goldseeker

    Goldseeker

    Joined:
    Apr 2, 2013
    Posts:
    42
    Do you have plans to provide proper SynchronizationContext for main thread or users will be expected to do it themselves?
     
  2. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    This is not something we have considered yet. Thanks for bringing it up though. I'll start a discussion internally about it.
     
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Don't forget to setup a synchronization context for edit-time as well, so that editor extensions could use async/await too.
     
    sp-LeventeLajtai likes this.
  4. Goldseeker

    Goldseeker

    Joined:
    Apr 2, 2013
    Posts:
    42
    In current version i was able to put together a simple synchronization context that dispatches everything to the next update, and i have to say async await syntax in UI looks glorious, both in multithreaded situations and cases when you just need to queue one animation after another.
     
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I've been playing with async/await for quite some time by now in my C# 6 integration hack project. I made there four separate synchronization contexts: for update, late update, fixed update and for the editor update. Not sure it's the best idea, but seems quite useful:

    - When you launch an async method from a FixedUpdate handler, you would probably expect to return back to FixedUpdate context when the async method finishes.
    - Another example is when you do something in the Update context and then want to switch to FixedUpdate for some reason. If there are multiple synchronization contexts, it's very easy to do:
    Code (CSharp):
    1.     private async void Update()
    2.     {
    3.         if (Input.GetKeyDown(KeyCode.Space))
    4.         {
    5.             await AsyncTools.ToFixedUpdate(); // switch to FixedUpdate context
    6.  
    7.             var rb = GetComponent<Rigidbody>();
    8.             var elapsedTime = 0f;
    9.             while (elapsedTime < 1)
    10.             {
    11.                 elapsedTime += Time.deltaTime;
    12.                 rb.AddForce(Vector3.up * 15);
    13.                 await 0; // wait for 0 seconds; since we are in FixedUpdate context, this effectively means wait for the next physics frame
    14.             }
    15.  
    16.             await AsyncTools.ToUpdate(); // switch to Update context
    17.             // do something in Update context
    18.        }
    19.     }
    20.  

    On the other hand, maybe it's overkill. We don't have this functionality now (except WaitForFixedUpdate) and I haven't seen any complaints.

    --
    I checked if the demo scenes and scripts from my C# 6 integration project work in this mono 4.6 preview, and they do work fine. So, here is a demo project that contains SynchronizationContexts, corresponding TaskSchedulers, a bunch of awaiters to make life easier, and also demo scripts and scenes for all new C# 5.0 and 6.0 features, including usage of Tasks and async/await in different combinations.
     

    Attached Files:

    Qbit86 and rakkarage like this.
  6. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Yeah, especially considering that coroutines can't be event handlers (their signatures are not compatible with UnityAction delegate) while async methods can:
    Code (CSharp):
    1.     public async void OnClickHandler()
    2.     {
    3.         button.interactable = false;
    4.  
    5.         await DoStuff();
    6.  
    7.         button.interactable = true;
    8.     }
     
    Last edited: Sep 29, 2016
    rakkarage likes this.
  7. Goldseeker

    Goldseeker

    Joined:
    Apr 2, 2013
    Posts:
    42
    @alexzzzz it looks really good, but maybe a bit to complicated to be included directly into engine, having this as a separate code would have been great, i'll have a better look at your project tomorrow.
     
  8. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    SynchronizationContext absolutely must be provided by Unity.

    Imagine, you write a complex code-heavy asset where you want to use async/await. If the context is not guaranteed, you either setup your own one or don't use async/await at all. If you do setup your own context, there could be a conflict with some other complex asset whose author also decided to provide a synchronization context for their own needs. The asset creators will fight each other for the right to setup the context.
     
    rakkarage likes this.
  9. MEGA64

    MEGA64

    Joined:
    Mar 27, 2013
    Posts:
    8
    I agree that unity should have its own synchronization context to properly utilize the functionality of async/await.
     
  10. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    @alexzzzz

    This looks really nice. The consensus on our side is that Unity will provide a SynchronizationContext. I'm not sure if we'll get it into the next preview drop but it should be coming soon.
     
    Qbit86, Thaina, Mazyod and 5 others like this.
  11. pvloon

    pvloon

    Joined:
    Oct 5, 2011
    Posts:
    591
    I'm not an expert on the async stuff but if unity has their own context does that mean we could potentially do stuff like:

    await UnityAsync.WaitForFixedUpdate();

    effectively replacing coroutines? If so that would be really amazing - coroutines needed some love and suddenly if that works in async we get coroutines on steroids :)


    EDIT: Reading alexz' post seems that is possible indeed, hurrah!
     
    Last edited: Sep 30, 2016
  12. jvlppm_pf

    jvlppm_pf

    Joined:
    Apr 24, 2016
    Posts:
    6
    Are you considering a synchronization context on the GameObject level?

    If there is a single SynchronizationContext GameObject every await would need to check if the current GameObject wasn't destroyed.

    I guess I should be able to use something like ConfigureAwait, so I can set the continuation to run even if the GameObject is no longer there (or even survive a scene change), but it should not be the default behavior.

    Just like when I use StartCoroutine and it runs on the current MonoBehaviour, await should do the same.

    And a ConfigureAwait extension could let me configure it to
    - continue even if the gameObject is not active (optionally wait for the GameObject to became active)
    - continue even if the gameObject was destroyed
    - continue even if the scene was changed
    - resume on update / lateUpdate / fixedUpdate

    Something like:

    Code (csharp):
    1.  
    2. ConfigureAwait(AsyncContext context, UpdateStep step)
    3.  
    4. enum AsyncContext {
    5.     ActiveBehaviour, // Default, wait until this MonoBehaviour is enabled to resume
    6.     Behaviour, // Resume even if the MonoBehaviour is not enabled (behaviour was not destroyed)
    7.     GameObject, // Runs even if the GameObject is not active (gameObject was not destroyed)
    8.     Scene, // Runs only if the scene is still active (scene was not destroyed)
    9.     Game // Resume ignoring scene changes (game is still running)
    10. }
    11.  
    12. enum UpdateStep {
    13.     Update, // Default
    14.     FixedUpdate,
    15.     LateUpdate
    16. }
    17.  
    and by not using ConfigureAwait it would use AsyncContext.ActiveBehaviour and resume on UpdateStep.Update.
     
  13. Goldseeker

    Goldseeker

    Joined:
    Apr 2, 2013
    Posts:
    42
    I think that synchronization context should not be bound to any gameobject or scene for that matter
     
    neonblitzer and Prodigga like this.
  14. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    This is a huge deal, it sucks to have to make something a mono behavior, and ensure the Gameobject it is attached to doesn't get destroyed etc just to have coroutine functionality.
     
  15. Jason-Verde

    Jason-Verde

    Joined:
    Mar 7, 2015
    Posts:
    2
    I like jvppm_pf's proposal. It's default reflects what people have come to expect from coroutines, but allows the flexibility to associate with the current scene or game. Synchronization contexts are exactly what their name says, contextual. Making them contextual to an active behaviour seems logical to me in an application current designed around code [almost] always running in the context of an active behaviour.

    I'm so excited for this upgrade btw. Love what you guys are doing.
     
    JoshPeterson likes this.
  16. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    I'm sharing this with you guys, it uses async and Task<T>; it's working great for me now.

    NOTE:
    • Used with v5.5.0b7
    • This is for the editor as I needed to run long operations within an editor window without blocking
    • I guess it could be upgraded for play mode but it has to be done
      (with a hidden GO/MonoBehaviour singleton instead of EditorApplication.Update ?)
    Improvements from the community are welcome !

    The context:

    Code (CSharp):
    1.  
    2. using System.Collections.Concurrent;
    3. using System.Linq;
    4. using System.Threading;
    5. using UnityEditor;
    6.  
    7. namespace Editor
    8. {
    9.     [InitializeOnLoad]
    10.     public sealed class UnitySynchronizationContext : SynchronizationContext
    11.     {
    12.         private static readonly ConcurrentQueue<Message> Queue;
    13.  
    14.         static UnitySynchronizationContext()
    15.         {
    16.             Queue = new ConcurrentQueue<Message>();
    17.             EditorApplication.update += Update;
    18.         }
    19.  
    20.         private static void Enqueue(SendOrPostCallback d, object state)
    21.         {
    22.             Queue.Enqueue(new Message(d, state));
    23.         }
    24.  
    25.         private static void Update()
    26.         {
    27.             if (!Queue.Any())
    28.                 return;
    29.  
    30.             Message message;
    31.  
    32.             if (!Queue.TryDequeue(out message))
    33.                 return;
    34.  
    35.             message.Callback(message.State);
    36.         }
    37.  
    38.         public override SynchronizationContext CreateCopy()
    39.         {
    40.             return new UnitySynchronizationContext();
    41.         }
    42.  
    43.         public override void Post(SendOrPostCallback d, object state)
    44.         {
    45.             Enqueue(d, state);
    46.         }
    47.  
    48.         public override void Send(SendOrPostCallback d, object state)
    49.         {
    50.             Enqueue(d, state);
    51.         }
    52.  
    53.         private sealed class Message
    54.         {
    55.             public Message(SendOrPostCallback callback, object state)
    56.             {
    57.                 Callback = callback;
    58.                 State = state;
    59.             }
    60.  
    61.             public SendOrPostCallback Callback { get; set; }
    62.             public object State { get; set; }
    63.         }
    64.     }
    65. }
    66.  
    The helper class:

    Code (CSharp):
    1. using System;
    2. using System.Threading;
    3. using System.Threading.Tasks;
    4. using JetBrains.Annotations;
    5. using UnityEditor;
    6.  
    7. namespace Editor
    8. {
    9.     [InitializeOnLoad]
    10.     public static class UnityTasks
    11.     {
    12.         private static readonly TaskScheduler Scheduler;
    13.  
    14.         static UnityTasks()
    15.         {
    16.             var context = new UnitySynchronizationContext();
    17.             SynchronizationContext.SetSynchronizationContext(context);
    18.             Scheduler = TaskScheduler.FromCurrentSynchronizationContext();
    19.         }
    20.  
    21.         public static Task Run([NotNull] Func<Task> func)
    22.         {
    23.             if (func == null)
    24.                 throw new ArgumentNullException("func");
    25.  
    26.             var task = Task.Factory
    27.                 .StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler)
    28.                 .Unwrap();
    29.  
    30.             return task;
    31.         }
    32.  
    33.         public static Task<T> Run<T>([NotNull] Func<Task<T>> func)
    34.         {
    35.             if (func == null)
    36.                 throw new ArgumentNullException("func");
    37.  
    38.             var task = Task.Factory
    39.                 .StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler)
    40.                 .Unwrap();
    41.  
    42.             return task;
    43.         }
    44.  
    45.         public static Task Run([NotNull] Action action)
    46.         {
    47.             if (action == null)
    48.                 throw new ArgumentNullException("action");
    49.  
    50.             var task = Task.Factory
    51.                 .StartNew(action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler);
    52.  
    53.             return task;
    54.         }
    55.  
    56.         public static Task<T> Run<T>([NotNull] Func<T> func)
    57.         {
    58.             if (func == null)
    59.                 throw new ArgumentNullException("func");
    60.  
    61.             var task = Task.Factory
    62.                 .StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler);
    63.  
    64.             return task;
    65.         }
    66.     }
    67. }
    68.  
    An example:

    Code (CSharp):
    1.  
    2. using System.Threading.Tasks;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. namespace Editor
    7. {
    8.     public class MyWindow : EditorWindow
    9.     {
    10.         [MenuItem("TEST/MyWindow")]
    11.         private static void Init()
    12.         {
    13.             GetWindow<MyWindow>();
    14.         }
    15.  
    16.         private async void OnGUI()
    17.         {
    18.             if (GUILayout.Button("Async"))
    19.             {
    20.                 await UnityTasks.Run(async () =>
    21.                 {
    22.                     for (var i = 0; i < 10; i++)
    23.                     {
    24.                         await Task.Delay(100);
    25.                         EditorUtility.CreateGameObjectWithHideFlags(i.ToString(), HideFlags.None);
    26.                     }
    27.                 });
    28.             }
    29.         }
    30.     }
    31. }
    32.  
     
    Last edited: Oct 26, 2016
    JoshPeterson likes this.
  17. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    @alexzzzz

    Sorry, I didn't read your first post well ... basically you've nailed it !

    Thanks for sharing this :)
     
  18. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    This is great. I tried to get synchronization contexts like this working at my previous job for an-inter process comm system. I eventually wound up scrapping my "tasks" implementation for a a lock-with-queue implementation, since Unity lacked the context support. Had this been in before we could of had the unity client and WPF server run the exact same async/await logic for processing the data packets.
     
  19. Mazyod

    Mazyod

    Joined:
    Apr 21, 2014
    Posts:
    25
    I hope someone could point us to a place where we can track the progress on this feature, perhaps on the issue tracker? I don't see anything about this on the roadmap.
     
  20. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    We don't have anything on the Issue tracker yet. On the roadmap, this is listed as ".NET Profile Upgrade" in the "Research" section. It has been there for a while though, so that won't give you any sense of progress.

    We're hoping to have another preview build, this time with a number of players, ready within the next few weeks. This build won't have anything specific to the SyncContext yet though, as we've not worked on that issue. However, it is coming up on our internal list of priorities.
     
    Mazyod likes this.
  21. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    It's going to be ready this month? So we can play with it during holidays :p
     
  22. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    Maybe! I'm not sure yet, but we're working on it.
     
  23. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    I wanted to let everyone on this thread know that the new preview build we shipped comes with a default synchronization context. Please give it a try and let us know your feedback. Thanks!
     
    alexzzzz and Mazyod like this.
  24. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Where does the execution of pending continuations of the main thread's synchronization context fit in the grand scheme of Event Execution Order?

    1. At the beginning of each frame (between Start and FixedUpdate on the chart).
    2. At the end of each frame (between OnApplicationPause and OnDisable on the chart).
    3. Somewhere around where coroutines are executed.
    4. Somewhere else.
    5. Undefined.
     
  25. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22
    Somewhere around where coroutines are executed :)
     
  26. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    789
    How much can we use the TPL (Task, async, await, ...) ?
    Like how performant is it?

    As far as I know every Task is a class, so that will allocate, are there any plans to somehow mitigate this?
    Or will it be slow and/or gc heavy - a thing for prototyping - like SendMessage, coroutines(gc alloc everytime one is started), ...
     
  27. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    @dadude123

    Everything in TPL should work. Task is a class now, and we don't have any way to work around that at the moment. We're stuck to the same class library interface as .NET and Xamarin. I believe that there is work at Microsoft to do struct based Task code, but that is probably a ways off.

    In general though, we have not looked at the performance of the TPL yet.
     
    dadude123 likes this.
  28. Genom

    Genom

    Joined:
    Dec 2, 2014
    Posts:
    86
    Hi guys, I'm wondering about WebGL and .net 4.6. As SAB in WebAssembly are far yet to be availble I understand that all related to Threads won't be available for WebGL:

    does it mean that we won't have .net 4.6 for a long time? or just that some parts of it (as it is ocurring now with threading) won't be?

    In another different question, but related to the .net 4.6, do you know if System.Remoting will be available in WebGL with the 4.6? I mention that because some guy has developed the XmlLayout framework and its MVVM depends on Remoting, and it is a very very useful and awesome initiative (still I'm wondering why Unity does not provide UI creation by xml) but it does not work for WebGL because of the lack of Remoting.

    cheers!
     
  29. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    @Genom

    Actually, nothing about the .NET 4.6 profile changes the WebGL platform restrictions that exist today. The latest experimental preview build does not have support for the WebGL player yet, but that is due to a bug in the .NET class libraries that perform an unaligned write to memory in unsafe code (something which is not allowed on WebGL with asm.js). Once we correct that bug, WebGL will get the full .NET 4.6 profile, minus the things it doesn't support now, like dynamic code generation and threading.

    We're not doing special to allow System.Remoting to work with WebGL. I think System.Remoting uses socket under the covers, and if so, it will continue to not work with WebGL because of that lack of sockets support.
     
  30. leftler

    leftler

    Joined:
    Feb 4, 2016
    Posts:
    9
    The struct based task (called ValueTask) is out on the Microsoft Github site and has been in the System.Threading.Tasks.Extensions NuGet package since version 4.1 (September of last year)
     
    Last edited: Feb 28, 2017
    alexzzzz likes this.
  31. MuckSponge

    MuckSponge

    Joined:
    Jul 11, 2015
    Posts:
    41
    Sorry for reviving, but this thread deserves it.

    @alexzzzz This is awesome work, thank you for sharing it :)

    One thing I did notice was when I tried to use your awaiters in a tight Update loop (so it pauses execution every frame, like having yield return null in an endless while loop) it would produce garbage every iteration, which you obviously don't want for endless tight loops.

    I came up with my own solution to this problem using value types and moving away from events. It's not the prettiest thing in the world but it works (in play mode). It's so satisfying being able to do away with IEnumerators!
     
  32. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    2017.1 is out and I thought I'd just give another try on how to get long running tasks in the editor, so far it works as expected and Progress<T> works as well, i.e. standard approach for reporting progress.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Concurrent;
    3. using System.Linq;
    4. using System.Threading;
    5. using System.Threading.Tasks;
    6. using UnityEditor;
    7. using UnityEngine;
    8.  
    9. namespace Editor
    10. {
    11.     /// <summary>
    12.     ///     Synchronization context for Unity editor.
    13.     /// </summary>
    14.     [InitializeOnLoad]
    15.     public sealed class UnityEditorSynchronizationContext : SynchronizationContext
    16.     {
    17.         #region Private types
    18.  
    19.         private sealed class Task
    20.         {
    21.             public Task(SendOrPostCallback callback, object state)
    22.             {
    23.                 Callback = callback;
    24.                 State = state;
    25.             }
    26.  
    27.             public SendOrPostCallback Callback { get; }
    28.  
    29.             public object State { get; }
    30.         }
    31.  
    32.         #endregion
    33.  
    34.         #region Static members
    35.  
    36.         private static readonly ConcurrentQueue<Task> Tasks;
    37.  
    38.         static UnityEditorSynchronizationContext()
    39.         {
    40.             Tasks = new ConcurrentQueue<Task>();
    41.  
    42.             EditorApplication.update += Update;
    43.  
    44.             var context = new UnityEditorSynchronizationContext();
    45.  
    46.             SetSynchronizationContext(context);
    47.         }
    48.  
    49.         private static void Enqueue(SendOrPostCallback callback, object state)
    50.         {
    51.             Tasks.Enqueue(new Task(callback, state));
    52.         }
    53.  
    54.         private static void Update()
    55.         {
    56.             if (!Tasks.Any())
    57.                 return;
    58.  
    59.             Task task;
    60.  
    61.             if (!Tasks.TryDequeue(out task))
    62.                 return;
    63.  
    64.             task.Callback(task.State);
    65.         }
    66.  
    67.         #endregion
    68.  
    69.         #region Instance members
    70.  
    71.         public override SynchronizationContext CreateCopy()
    72.         {
    73.             return new UnityEditorSynchronizationContext();
    74.         }
    75.  
    76.         public override void Post(SendOrPostCallback callback, object state)
    77.         {
    78.             Enqueue(callback, state);
    79.         }
    80.  
    81.         public override void Send(SendOrPostCallback callback, object state)
    82.         {
    83.             Enqueue(callback, state);
    84.         }
    85.  
    86.         #endregion
    87.     }
    88.  
    89.     internal class UnityEditorSynchronizationContextTest : EditorWindow
    90.     {
    91.         private int _progress;
    92.         private IProgress<int> _progressHandler;
    93.  
    94.         [MenuItem("UnityEditorSynchronizationContext/Test")]
    95.         private static void Init()
    96.         {
    97.             GetWindow<UnityEditorSynchronizationContextTest>();
    98.         }
    99.  
    100.         private void Update()
    101.         {
    102.             Repaint();
    103.         }
    104.  
    105.         private void OnEnable()
    106.         {
    107.             _progressHandler = new Progress<int>(s => _progress = s);
    108.         }
    109.  
    110.         private async void OnGUI()
    111.         {
    112.             EditorGUI.ProgressBar(GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.button), _progress / 10.0f, string.Empty);
    113.  
    114.             if (GUILayout.Button("Test"))
    115.             {
    116.                 await Task.Run(async () =>
    117.                 {
    118.                     for (var i = 0; i < 10; i++)
    119.                     {
    120.                         await Task.Delay(1000);
    121.                         _progressHandler.Report(i + 1);
    122.                     }
    123.                 });
    124.             }
    125.         }
    126.     }
    127. }
    That's all there is to it, it should be perfected, though.

    So, the question now,

    Can we hope to get some facility out of the box that supports such scenarios in the future ?

    EDIT:

    After some use,

    It will definitely definitely a kind of Dispatcher like there is in WPF, to allow calling GUI methods and avoid errors such as : UnityException: DisplayDialog can only be called from the main thread.
     
    Last edited: Jul 21, 2017
  33. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    @aybe

    We don't have any plans to ship something like this with Unity at the moment. It does look interesting though. In general, we're taking a wait-and-see approach to using async and await in features shipped with the engine. These tools can provide a very nice developer experience, but they also generate a lot of code and many allocations behind the scenes. So we're waiting to see how developers use them to determine if we want to incorporate things like this into the engine.

    Do you need any support from Unity to make this work though? It looks like this works well as a solution in script code, but it something from our side could facilitate it, we can look at doing that.
     
  34. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    1. It looks like nothing will change if we remove async and await keywords from OnGUI itself, since we await the task but don't do anything after that.

    2. There's no need to set up your own SynchronizationContext for the editor. You can force the built-in one to do its job.
    Code (CSharp):
    1. [InitializeOnLoadMethod]
    2. private static void Initialize()
    3. {
    4.     var context = SynchronizationContext.Current;
    5.     var execMethod = context.GetType().GetMethod("Exec", BindingFlags.Instance | BindingFlags.NonPublic);
    6.     var execute = (EditorApplication.CallbackFunction)execMethod.CreateDelegate(typeof(EditorApplication.CallbackFunction), context);
    7.  
    8.     EditorApplication.update += execute;
    9. }
     
    SugoiDev and iwaldrop like this.
  35. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
  36. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    @alexzzzz

    1. I somehow understand but not completely, since nothing is done after logically they're not needed but the infrastructure requires it, correct ?
    2. Thanks, I didn't know about that; used your code snippet in the feature request I've posted above, hopefully we'll get an official solution soon !
     
  37. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I mean, if we take the previous OnGUI example as it is, we don't need to wait for Task.Run completion, because the method just ends after that, doing nothing useful. So we don't even need a synchronization context, because there's nothing to synchronize.
    Code (CSharp):
    1. private void OnGUI()
    2. {
    3.     EditorGUI.ProgressBar(GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.button), _progress / 10.0f, string.Empty);
    4.  
    5.     if (GUILayout.Button("Test"))
    6.     {
    7.         Task.Run(...whatever...);
    8.     }
    9. }
    If we do something useful after the task completes (for example save the results), then we will need to await the task and here the synchronization context comes into play.
    Code (CSharp):
    1. private async void OnGUI()
    2. {
    3.     EditorGUI.ProgressBar(GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.button), _progress / 10.0f, string.Empty);
    4.  
    5.     if (GUILayout.Button("Test"))
    6.     {
    7.         var task = Task.Run(...whatever...);
    8.          
    9.         await task;
    10.  
    11.         AssetDatabase.CreateAsset(...);
    12.         AssetDatabase.SaveAssets();
    13.         return;
    14.     }
    15.     ...
    16. }
    It would work as expected, if UnitySynchronizationContext executed the continuations from its job queue always, not only in play mode as it is now.
     
    Last edited: Jul 25, 2017
  38. aybeone

    aybeone

    Joined:
    May 24, 2015
    Posts:
    107
    Actually we need a context unless I really screwed things out:

    If there's no context then UI will simply not update even though we repaint at each Update(), instead what happens is that it will update at its will or when you Alt-Tab.

    I've even checked by restarting Unity with and without the editor context to make sure that was the cause ...

    Does it work out of the box on your side ?
     
  39. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    250
    Anybody know why exceptions get logged to the console in this case:

    Code (csharp):
    1.  
    2. public class AwaitTest : MonoBehaviour
    3. {
    4.     public void Start()
    5.     {
    6.         RunTest();
    7.     }
    8.  
    9.     async void RunTest()
    10.     {
    11.         throw new Exception("Something bad happened!");
    12.     }
    13. }
    14.  
    But in this case nothing gets logged:

    Code (csharp):
    1.  
    2. public class AwaitTest : MonoBehaviour
    3. {
    4.     public void Start()
    5.     {
    6.         RunTest();
    7.     }
    8.  
    9.     async Task RunTest()
    10.     {
    11.         throw new Exception("Something bad happened!");
    12.     }
    13. }
    14.  
    I'd like to use Task so that my async methods can optionally be called from other async methods instead of void, but when I do that and then call it directly from non-async code, all errors get suppressed
     
  40. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    the Task object will catch and store any exception thrown, and they will be rethrown when you await the task.
    a void async method has no task, so the exception is thrown directly (and Unity logs it)

    something like this should work:
    Code (CSharp):
    1.  
    2. public class AwaitTest : MonoBehaviour
    3. {
    4.     public async void Start()
    5.     {
    6.         await RunTest();
    7.     }
    8.     async Task RunTest()
    9.     {
    10.         throw new Exception("Something bad happened!");
    11.     }
    12. }
    or you can manually hook into the task completion and log the exception if there is one

    also, any code inside async methods that executes before the first await is executed syncronously (so your async void RunTest is not async at all!)
     
    mkderoy, alexzzzz and eventropy like this.
  41. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    250
    I ended up taking a deeper dive into the problems with async await in Unity 2017 and posted my findings here
     
  42. CPlusSharp22

    CPlusSharp22

    Joined:
    Dec 1, 2012
    Posts:
    111
    This is still a problem to this day I think.
    I'm on Unity 2019.3.0f5 and only in the editor are we getting issues with SynchronizationContext and awaits. The whole editor will freeze if you have a background thread that awaits something indefinitely. Super annoying! This doesn't happen in an executable that the Unity Editor builds out because that presumably guarantees the game on the main thread, while in the Editor, there's some "magic" with threads running the game.

    We have an async void method that houses a while loop that should go indefinitely until shutdown. The method itself awaits an API that uses Task. We call the async method from the "main thread" and move on with regular unity stuff. 90% of the time this is fine. But if you pause with a debugger attached to the editor or something that triggers Unity to do something funky with the SynchronizationContext, suddenly the editor will "think" the async method is the main game thread and freeze the whole editor.

    I don't know if that's exactly what's technically happening, just a guess based on the debugger and many many situations where this occurs. The only way to unfreeze the editor is to force an exception at that point or task manager unity.exe to the ground.