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

Async/Await in Editor script?

Discussion in 'Experimental Scripting Previews' started by Foriero, Jul 3, 2017.

  1. Foriero

    Foriero

    Joined:
    Jan 24, 2012
    Posts:
    584
    Hello, how does unity synchronization to the main editor thread works? Can we have a simple example?
     
  2. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    It seems that UnitySynchronizationContext is set as the current context for the main thread in edit-time, but doesn't really do much. The continuations are accumulated but never get executed, except the moment when the editor starts script recompilation process.

    In this example the code after await (line 39) doesn't run until I press "execute pending continuations" button. In play-time it works as expected though since the pending continuations are executed every frame.

    No idea whether this behavior is intentional or not.
    Code (CSharp):
    1. using System.Reflection;
    2. using System.Threading;
    3. using System.Threading.Tasks;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public class Foo : MonoBehaviour
    8. {
    9. }
    10.  
    11. [CustomEditor(typeof(Foo))]
    12. public class FooEditor : Editor
    13. {
    14.     public override async void OnInspectorGUI()
    15.     {
    16.         base.OnInspectorGUI();
    17.  
    18.         if (GUILayout.Button("Execute pending continuations"))
    19.         {
    20.             var context = SynchronizationContext.Current;
    21.             var execMethod = context.GetType().GetMethod("Exec", BindingFlags.NonPublic | BindingFlags.Instance);
    22.             execMethod.Invoke(context, null);
    23.         }
    24.  
    25.         if (GUILayout.Button("Post"))
    26.         {
    27.             SynchronizationContext.Current.Post(_ => Debug.Log("Submitted via Post"), null);
    28.         }
    29.  
    30.         if (GUILayout.Button("Send"))
    31.         {
    32.             SynchronizationContext.Current.Send(_ => Debug.Log("Submitted via Send"), null);
    33.         }
    34.  
    35.         if (GUILayout.Button("Do time consuming stuff"))
    36.         {
    37.             Debug.Log("before: " + Thread.CurrentThread.ManagedThreadId);
    38.             await Task.Run(() => DoTimeConsumingStuff());
    39.             Debug.Log("after: " + Thread.CurrentThread.ManagedThreadId);
    40.         }
    41.     }
    42.  
    43.     private void DoTimeConsumingStuff()
    44.     {
    45.         Debug.Log("doing...");
    46.         Thread.Sleep(1000);
    47.         Debug.Log("done: " + Thread.CurrentThread.ManagedThreadId);
    48.     }
    49. }
     
    Last edited: Jul 3, 2017
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    This example works as expected in edit-time. The continuations don't accumulate and get executed when there are any. (I would consider it a hack though.)
    Code (CSharp):
    1. using System.Reflection;
    2. using System.Threading;
    3. using System.Threading.Tasks;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public class Foo : MonoBehaviour
    8. {
    9. }
    10.  
    11. [CustomEditor(typeof(Foo))]
    12. public class FooEditor : Editor
    13. {
    14.     [InitializeOnLoadMethod]
    15.     private static void Initialize() => EditorApplication.update += ExecuteContinuations;
    16.  
    17.     public override async void OnInspectorGUI()
    18.     {
    19.         base.OnInspectorGUI();
    20.  
    21.         if (GUILayout.Button("Do time consuming stuff"))
    22.         {
    23.             Debug.Log("before: " + Thread.CurrentThread.ManagedThreadId);
    24.             await Task.Run(() => DoTimeConsumingStuff());
    25.             Debug.Log("after: " + Thread.CurrentThread.ManagedThreadId);
    26.             return;
    27.         }
    28.  
    29.         if (GUILayout.Button("Post"))
    30.         {
    31.             SynchronizationContext.Current.Post(_ => Debug.Log("Submitted via Post"), null);
    32.         }
    33.  
    34.         if (GUILayout.Button("Send"))
    35.         {
    36.             SynchronizationContext.Current.Send(_ => Debug.Log("Submitted via Send"), null);
    37.         }
    38.     }
    39.  
    40.     private static void ExecuteContinuations()
    41.     {
    42.         var context = SynchronizationContext.Current;
    43.         var execMethod = context.GetType().GetMethod("Exec", BindingFlags.NonPublic | BindingFlags.Instance);
    44.         execMethod.Invoke(context, null);
    45.     }
    46.  
    47.     private void DoTimeConsumingStuff()
    48.     {
    49.         Debug.Log("doing...");
    50.         Thread.Sleep(1000);
    51.         Debug.Log("done: " + Thread.CurrentThread.ManagedThreadId);
    52.     }
    53. }
    This example also demonstrates that async/await doesn't work nicely with the immediate mode gui. Notice that return statement in line 26. If you comment it, the first time you call something gui-related you'll get "ArgumentException: You can only call GUI functions from inside OnGUI" because the continuation doesn't run "from inside OnGUI" but rather from inside EditorApplication.update in this case.
     
    Last edited: Jul 3, 2017
    rakkarage likes this.
  4. joncham

    joncham

    Unity Technologies

    Joined:
    Dec 1, 2011
    Posts:
    276
    This is not intentional. We'll need to look into supporting async/await properly when not in play mode.
     
  5. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    Thanks to Alexzzzz's sample, I've created a standalone class you can add to a Unity project that will enable the update pump for edit mode:
    Code (CSharp):
    1.  
    2. using System.Reflection;
    3. using System.Threading;
    4. using UnityEditor;
    5. namespace UI
    6. {
    7.     public class EditorAsyncPump
    8.     {
    9.         [InitializeOnLoadMethod]
    10.         private static void Initialize()
    11.         {
    12.             EditorApplication.update += ExecuteContinuations;
    13.         }
    14.         private static void ExecuteContinuations()
    15.         {
    16.             if (EditorApplication.isPlayingOrWillChangePlaymode)
    17.             {
    18.                 // Not in Edit mode, don't interfere
    19.                 return;
    20.             }
    21.             var context = SynchronizationContext.Current;
    22.             if (_execMethod == null)
    23.             {
    24.                 _execMethod = context.GetType().GetMethod("Exec", BindingFlags.NonPublic | BindingFlags.Instance);
    25.             }
    26.             _execMethod.Invoke(context, null);
    27.         }
    28.         private static MethodInfo _execMethod;
    29.     }
    30. }
    31.  
     
    MCPGNZ likes this.
  6. RDeluxe

    RDeluxe

    Joined:
    Sep 29, 2013
    Posts:
    117
    Any news on this ? The pump is working, but I suspect it to be responsible for the Unity editor crashs that's I'm getting.
     
  7. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,539
    Yeah same issue as well.

    @joncham or @JoshPeterson any word on this?

    Also don't know if I should make another thread, but just wondering if using AssetPostprocessor's messages like OnPostprocessModel will properly properly handle being set as Task instead of void? It seems to compile fine if I make it an async Task, but the AssetPostprocessor doesn't seem to actually await the OnPostprocessModel, and ends up processing multiple assets at once. We do some heavy processing to various imported assets that would help to have async in the postprocessor, but obviously don't want to create issues by not having the pipeline run linearly.
     
  8. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    We've not corrected this yet. To help us out, could someone submit a bug report for async/await in the editor? That will give us all the details we need in one place, and it should prevent us from forgetting about this. :)

    This is probably some time away yet. In most of the Unity C# code/API we can't start using new C# features until we no longer support the old scripting runtime. We're hoping to be able to deprecate the old scripting runtime after a few major Unity releases, but that will still be on the order of months in the best case. Only then can we start to work on new C# features in the Unity API.
     
  9. RDeluxe

    RDeluxe

    Joined:
    Sep 29, 2013
    Posts:
    117
    Hello Josh,

    It's pretty simple ; without the Pump a Task won't work except if the user moves the mouse / force an editor refresh, then the task will execute.

    I could create a test repo if you like, but there is not much to show : it's simply not working (without the aforementioned pump)
     
  10. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    Please do create a project to reproduce this and submit a bug report. Thanks!
     
  11. RDeluxe

    RDeluxe

    Joined:
    Sep 29, 2013
    Posts:
    117
    Alright, I just thought you knew as @joncham clearly stated that async/await was not working when not in Play Mode.
     
  12. StephenHodgson-Valorem

    StephenHodgson-Valorem

    Joined:
    Mar 8, 2017
    Posts:
    148
    Hey guys, any update on this?
     
  13. VOTRUBEC

    VOTRUBEC

    Joined:
    Dec 17, 2014
    Posts:
    106
    That moment when you think to yourself "Everything's correct, but, it's almost as if 'await' isn't working in an Editor Windows. Let me check Google..."
     
  14. mkderoy

    mkderoy

    Unity Technologies

    Joined:
    Oct 25, 2016
    Posts:
    22
    Changes to support async/await in edit mode are in the process of being landed in trunk at the moment
     
  15. Dissolute

    Dissolute

    Joined:
    Oct 6, 2012
    Posts:
    2
    Was this released? I'm still encountering this issue when running EditMode tests in Unity 2018.2.0f2.

    Never mind, I was deadlocking myself. This simple test works fine:

    Code (CSharp):
    1.         [Test]
    2.         public async void When_Simple_Async_Test()
    3.         {
    4.             UnityEngine.Debug.Log("SC before:");
    5.             UnityEngine.Debug.Log(SynchronizationContext.Current);
    6.             await Task.Delay(10);
    7.             UnityEngine.Debug.Log("SC after:");
    8.             UnityEngine.Debug.Log(SynchronizationContext.Current);
    9.         }
     
    Last edited: Aug 12, 2018
  16. IC_

    IC_

    Joined:
    Jan 27, 2016
    Posts:
    61
    Useless test. Increase delay and add an assertion depending on task result. It will success in test runner and then throw an assertion error in console after delay
     
  17. awesomesaucelabs

    awesomesaucelabs

    Joined:
    Aug 6, 2019
    Posts:
    189
    For latecomers to this thread:

    It looks like the problems with using async/await in the Editor are fixed now. I ran @alexzzzz's test code (from post #2) in Unity 2018.3.0f2 and async/await worked correctly (without using the "pump").

    FYI, the issue discussed in this thread was submitted to the Unity issue tracker here. It was marked as a duplicate of another issue (the connection between the two issues is not obvious to me), and that issue was marked as "fixed" in Unity 2018.2.
     
  18. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    42
    Is there any update for exception :"ArgumentException: You can only call GUI functions from inside OnGUI".
    as the continuation not occur on the main thread's OnGUI/OnInspectorGUI.
     
    Last edited: Oct 27, 2019
  19. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    I believe that this should be working now. If it is not, can you drop us a new bug report for it?
     
  20. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    42
    Code (CSharp):
    1. using System.Threading;
    2. using System.Threading.Tasks;
    3. using UnityEditor;
    4. using UnityEngine;
    5. public class Foo : MonoBehaviour { }
    6. [CustomEditor(typeof(Foo))]
    7. public class FooEditor : Editor
    8. {
    9.     public override async  void OnInspectorGUI()
    10.     {
    11.         if (GUILayout.Button("Do time consuming stuff"))
    12.         {
    13.             Debug.Log("before: " + Thread.CurrentThread.ManagedThreadId);
    14.             await Task.Run(() => DoTimeConsumingStuff()); // when await task finished here, an error will rise when draw a GUI below 。
    15.             //var _ = Task.Run(() => DoTimeConsumingStuff()); //but this one will not throw any error.
    16.             Debug.Log("after: " + Thread.CurrentThread.ManagedThreadId);
    17.         }
    18.  
    19.         // when draw this button , error occured ,if you use await before .
    20.         if (GUILayout.Button("Send")){}
    21.     }
    22.  
    23.     private void DoTimeConsumingStuff()
    24.     {
    25.         Debug.Log("In task: " + Thread.CurrentThread.ManagedThreadId);
    26.     }
    27. }
    Reproduce with unity 2018.2.16 and unity 2019.1.14
     
    Last edited: Oct 31, 2019
  21. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    Thanks, can you submit this in a bug report? That way we can feedback to you when it is fixed.
     
  22. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    42
    what is the "bug report" ,and what should i do then?
     
  23. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,920
    Bshsf_9527 likes this.
  24. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    42
    This page has no 404 error but blank,I can do nothing,by the way I visited this website in China..
     
  25. Bshsf_9527

    Bshsf_9527

    Joined:
    Sep 6, 2017
    Posts:
    42
    JoshPeterson likes this.
  26. pld

    pld

    Joined:
    Dec 12, 2014
    Posts:
    7
    @Bshsf_9527, I don't think it's a bug. I can think of no way for Unity to implement immediate-mode GUI that works properly with async code. What you're doing is essentially this:

    Code (CSharp):
    1. void OnInspectorGUI() {
    2.   GUI.BeginHorizontal();
    3.   SynchronizationContext.Current.Post( () => GUI.EndHorizontal() );
    4. }
    OnInspectorGUI's going to be called every frame; and the caller will first of all complain that you've started but not ended a horizontal block. And some time later, EndHorizontal() will complain that it's being called at some unexpected time.
     
  27. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Is anyone successfully getting async/await to work with UIToolkit/UIElements?

    I'm getting a silent crash (could be a hang, could be termination - there's no console errors) every time any async method even reads from the UIToolkit VisualElement tree (in Unity 2019.4)
     
    watsonsong likes this.
  28. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    Me too, is there any progress on this?
     
  29. TahneeSmith

    TahneeSmith

    Joined:
    Oct 27, 2016
    Posts:
    5

    I also am having issues with async functions being called in editor-time with UI elements. I couldn't find any existing bug reports on this...
     
    ENOUGH- likes this.
  30. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    I think in the end I just worked around it by avoiding lots of UIToolkit API calls that should work but clearly don't. If you can make a small reproducible case: please file a bug report (I am a bit frustrated with how often I file bug reports on UIToolkit that get rejected for ideological reasons, nothing to do with whether or not the bug is real or reproducible (usually mine are 100% reproducible)).

    Next time I'm working on MT code in UIToolkit I'll try to make a small repro case - maybe it's me misusing the Unity API's (and it's my own fault :)) but if not: should be possible to make a small example.
     
  31. PhantomGingerGames

    PhantomGingerGames

    Joined:
    Jun 23, 2019
    Posts:
    4
    Any updates on this? I'm seeing the same thing, I'm seeing the same.
     
  32. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    More than 2 years ago @JoshPeterson said: file a bug report. I've commented on it saying the same. Have you filed a bug report?

    If not ... why not? Why won't you do this if you want it fixed?
     
  33. DeathPro

    DeathPro

    Joined:
    Jul 28, 2018
    Posts:
    87
    After 6 years, Is there any update???
     
  34. NestorVG

    NestorVG

    Joined:
    Feb 12, 2019
    Posts:
    30
  35. simon-ferquel-unity

    simon-ferquel-unity

    Unity Technologies

    Joined:
    Apr 1, 2021
    Posts:
    67
    The original issue has been resolved AFAIK. However some of the example in that thread try to mix UI code that should run within a single update, and async code. As soon as you await on anything async, your method will resume on next update (not within the same update), and that is not supported by UI code.