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

Help with async await

Discussion in '2017.1 Beta' started by makeshiftwings, May 9, 2017.

  1. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Can someone give me a brief overview of how to best use async/await with Unity? I'm still having trouble wrapping my brain around it. How do we access the Unity SynchronizationContext? How would one replace a Coroutine with an async method? How do we tell an async method to run on the main thread vs a background thread? Is there an equivalent of "yield return null" that you can call from an async method in order to wait until the next Update frame?
     
    neonblitzer and Karsten like this.
  2. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    I'm also interested in this.

    I want to replace Coroutines.
     
    arufolo likes this.
  3. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Here's what I think I've figured out but I could be wrong:

    1) If you just call an async method it runs normally until you hit an await that actually needs to await*, at which point it will yield back to the caller like await does, which will finish the current Update frame or Start method or wherever you called the async method from. When the await is done awaiting, it automatically uses Unity SynchronizationContext and continues from where it left off some frames later, at some point around the same time as coroutines get updated, and continues running on the main thread.

    2) Await will actually just return immediately if the task you give it is already complete, which means if you want to actually wait a frame you have to await on something guaranteed to cause a wait. I've been using Task.Delay(1), which seems to be roughly similar to "yield return null" from a coroutine: it will yield back so that the Update finishes and come back during the next Update.

    3) Task.Yield() and Task.Delay(0) both seem to return immediately and don't yield, which seems different than what I've read on the internets, but maybe I'm dumb and not understanding. So don't use those.

    So if you just want to replace a coroutine with an async method:
    Code (csharp):
    1. void Start() {
    2.     StartCoroutine(DoThingsCoroutine());
    3. }
    4.  
    5. IEnumerator DoThingsCoroutine() {
    6.     while (something) {
    7.         Debug.Log("Doing stuff " + Time.deltaTime);
    8.         yield return null;
    9.    }
    10. }
    becomes

    Code (csharp):
    1. void Start() {
    2.     DoThingsAsync();
    3. }
    4.  
    5. async Task DoThingsAsync() {
    6.     while (something) {
    7.         Debug.Log("Doing stuff " + Time.deltaTime);
    8.         await Task.Delay(1);
    9.     }
    10. }
    Am I doing it right or is this stupid? It seems to work but I feel like maybe I'm going in the wrong direction.
     
    neonblitzer likes this.
  4. MEGA64

    MEGA64

    Joined:
    Mar 27, 2013
    Posts:
    8
    Docs:
    https://docs.microsoft.com/en-us/dotnet/articles/csharp/async

    Simple sample:

    Code (CSharp):
    1.     class TestAsync : Monobehaviour
    2.     {
    3.         void Start()
    4.         {
    5.             DoTaskAsync();
    6.         }
    7.        
    8.         public async void DoTaskAsync()
    9.         {
    10.             await Task.Run(()=>
    11.             {
    12.                 //a long-running operation...
    13.             });
    14.         }
    15.     }
     
  5. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I already know that doesn't work; anything you put inside of Task.Run gets moved to a new thread on the ThreadPool, where it will throw an "EnsureMainThread" exception as soon as you try to access anything in the scene. Plus, it's doubly bad because the exception itself will just get swallowed and the whole thing will just silently fail, because you're not returning a Task from the async method, so there is no way to wrap it in a try/catch or check the state of the task.
     
    Lorrak, RyT_13, Psyco92 and 2 others like this.
  6. buFFalo94

    buFFalo94

    Joined:
    Sep 14, 2015
    Posts:
    273
    I've tried things with async and await without success so i ended up with something like this
    Code (CSharp):
    1.  
    2. public void myVoid()
    3. {
    4.         Task task = new Task(() => mylongTask());
    5.         task.Start();
    6.         task.Wait();
    7. }
    8. private task mylongTask()
    9. {
    10. }
    11.  
    on windows i can see all of my cpu cores being used sometimes at 80-90% with this method
     
  7. MasoInar

    MasoInar

    Joined:
    Feb 20, 2014
    Posts:
    125
    I made simple (and silly) example how to use tasks. It doesn't replace coroutines since you can't access many unity features inside task, because they need to be run in main thread. However, it might still be useful though, if you need to do some heavy calculations, I/O operations and stuff like that.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Threading.Tasks;
    4.  
    5. public class TaskExample : MonoBehaviour {
    6.     float _elapsed = 0;
    7.     void Start()
    8.     {
    9.         Debug.Log("start begin");
    10.  
    11.         var tcs = new TaskCompletionSource<Vector3>();
    12.         Task.Run(async () =>
    13.         {
    14.             Debug.Log("Task started");
    15.             tcs.SetResult(await LongRunningTask());
    16.             Debug.Log("Task stopped");
    17.         });
    18.      
    19.         // ConfigureAwait must be true to get unity main thread context
    20.         tcs.Task.ConfigureAwait(true).GetAwaiter().OnCompleted(() =>
    21.         {
    22.             Debug.Log("task completed");
    23.             transform.Rotate(tcs.Task.Result);
    24.         });
    25.  
    26.         Debug.Log("start end");
    27.     }
    28.  
    29.     void Update()
    30.     {
    31.         if (_elapsed > 1)
    32.         {
    33.             Debug.Log($"update loop running " + Time.realtimeSinceStartup);
    34.             _elapsed = 0;
    35.         }
    36.         _elapsed += Time.deltaTime;
    37.     }
    38.  
    39.     async Task<Vector3> LongRunningTask()
    40.     {
    41.         var rand = new System.Random();
    42.         var v = Vector3.zero;
    43.         // do something that takes long time
    44.         for (var i = 0; i < 30000000; i++)
    45.         {
    46.             v = new Vector3(rand.Next(0, 360), rand.Next(0, 360), rand.Next(0, 360));
    47.         }
    48.         return v;
    49.     }
    50. }
    51.  
     
    ZiadJ likes this.
  8. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    All of these examples are how to create a background thread where you can't access Unity objects, so they're not particularly useful in my case. Here's an example coroutine that fades in the alpha on an image:

    Code (csharp):
    1. public IEnumerator FadeIn(Image image) {
    2.   while(image.color.a < 1f) {
    3.     image.color.a += speed * Time.deltaTime;  //needs to be on the main thread
    4.     yield return null;  //needs to regularly unblock so that the game keeps rendering
    5.    }
    6. }
    What's the best way to convert that into an async method? Like I said in the third post, I got it working by converting "yield return null" into "await Task.Delay(1)", and by calling the async method directly (without Task.New or task.Start()) but I'm not sure if this is dumb. I feel like "await Task.Delay(1)" is arbitrary since I don't actually care about waiting 1 millisecond, I'm just trying to force the await to yield control. And calling an async method directly gives me Resharper warnings about how I'm being stupid and shouldn't do that, and I feel sad when Resharper yells at me, even if the code technically works. :(
     
    neonblitzer likes this.
  9. Dizzy-Dalvin

    Dizzy-Dalvin

    Joined:
    Jul 4, 2013
    Posts:
    54
    @makeshiftwings No, you're doing it right in your example. I don't know the reason why Task.Yield() is not working, it seems like it should, if I understand it correctly.

    I also think it's wrong to think about tasks as if they're always executed on background threads, because by default they're not. It's specifically Task.Run(...) that does this.
    Compare these:

    Code (CSharp):
    1.  
    2.     Task<string> LoadAsync(string path)
    3.     {
    4.         return Task.Run(() => File.ReadAllText(path));
    5.     }
    6.  
    7.     async Task LoadThroughTask(string path)
    8.     {
    9.         // here our method LoadAsync spawns a new thread through Task.Run to load the file in background
    10.         var loadingTask = LoadAsync(path);
    11.  
    12.         while (!loadingTask.IsCompleted)
    13.             await Task.Delay(1);
    14.  
    15.         var text = loadingTask.Result;
    16.         Debug.Log(text);
    17.     }
    18.  
    19.  
    20.  
    21.     IEnumerator LoadThroughCoroutine(string path)
    22.     {
    23.         // here Resources.LoadAsync spawns a new thread to load the resource in background
    24.         var loadingOperation = Resources.LoadAsync<TextAsset>(path);
    25.  
    26.         while (!loadingOperation.isDone)
    27.             yield return null;
    28.  
    29.         var text = ((TextAsset)loadingOperation.asset).text;
    30.         Debug.Log(text);
    31.     }
    32.  
    As you can see, tasks are actually pretty similar to coroutines. You can rewrite both of these methods like this:

    Code (CSharp):
    1.     async Task LoadThroughTask(string path)
    2.     {
    3.         var loadingTask = LoadAsync(path);
    4.         await loadingTask;
    5.  
    6.         var text = loadingTask.Result;
    7.         Debug.Log(text);
    8.     }
    9.  
    10.     IEnumerator LoadThroughCoroutine(string path)
    11.     {
    12.         // here Resources.LoadAsync spawns a new thread to load the resource in background
    13.         var loadingOperation = Resources.LoadAsync<TextAsset>(path);
    14.         yield return loadingOperation;
    15.  
    16.         var text = ((TextAsset)loadingOperation.asset).text;
    17.         Debug.Log(text);
    18.     }
    But the main advantage of tasks (awaiters) is that you can use return values directly (which you cannot do with coroutines):

    Code (CSharp):
    1.     async Task LoadThroughTask(string path)
    2.     {
    3.         var text = await LoadAsync(path);
    4.         Debug.Log(text);
    5.     }
    You can also create tasks by other means, for example TaskCompletionSource. Here everything happens on the main thread:

    Code (CSharp):
    1.     TaskCompletionSource<string> _taskCompletionSource;
    2.     Button _button;
    3.     InputField _input;
    4.  
    5.     Task<string> GetUserName()
    6.     {
    7.         _taskCompletionSource = new TaskCompletionSource<string>();
    8.         _button.gameObject.SetActive(true);
    9.         _input.gameObject.SetActive(true);
    10.  
    11.         _button.onClick.AddListener(() => _taskCompletionSource.SetResult(_input.text));
    12.  
    13.         return _taskCompletionSource.Task;
    14.     }
    15.  
    16.     async Task Test()
    17.     {
    18.         Debug.Log("this happens before showing the button and the input field");
    19.         var userName = await GetUserName();
    20.         Debug.Log("user has pressed the button: " + userName);
    21.     }
    You can create tasks from other tasks:

    Code (CSharp):
    1.     async Task Test()
    2.     {
    3.         var userNameTask = GetUserName();
    4.         var loadTask = LoadAsync("text.txt");
    5.         var combinedTask = Task.WhenAll(userNameTask, loadTask);
    6.         await combinedTask;
    7.         Debug.Log("user has pressed the button: " + userNameTask.Result + " and the file has loaded: " + loadTask.Result);
    8.     }
     
    Last edited: May 11, 2017
  10. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    I think that we need Unity official examples of this approach in order to substitute Coroutines with this so we can be for sure what is the correct replacement.
     
    Lorrak likes this.
  11. Ferazel

    Ferazel

    Joined:
    Apr 18, 2010
    Posts:
    517
    I mean I am all for learning new things but besides the lack of return values from Coroutines is there a reason you're adamant that they're bad and need to be replaced with await/async?
     
  12. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,646
    I don't understand one thing: why do you want to replace that with an async method? Async is most useful when you do the heavy processing on another thread (but yes, you can only do that with objects that don't derive from UnityEngine.Object). But you don't want to do that, and coroutine works just fine in your case.
     
    Lorrak likes this.
  13. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Because I originally thought async would result in cleaner and faster code, since we'd be avoiding the overhead of allocating Coroutine enumerators and objects. I have to disagree with the idea that async is only useful for doing heavy processing on another thread. THREADS are useful for doing heavy processing on another thread, but we could already use threads just fine with ThreadPool.QueueUserWorkItem. Microsoft came up with the whole async/await and Task framework to allow lots of different ways of doing things asynchronously, and I'd even go so far as to say that Task.Run(() => SomeStuff()) is not even the main point of async, it's just an easy example for people who are used to typing ThreadPool.QueueUserWorkItem. But I refuse to believe that MS spent years working on this framework if the only point of it is that it's faster to type Task.Run than ThreadPool.QueueUserWorkItem. ;)

    Things that need to take place over several frames seem ideal for async, but due to some of Unity's weirdness it seems hard to get it to work as I'd expect. If we have async methods that were similar to the existing built-in coroutines I think it would make more sense, like:
    Code (csharp):
    1. await UnityAsync.NextUpdate;
    2. await UnityAsync.NextFixedUpdate;
    3. await UnityAsync.NextLateUpdate;
    Those make more sense than Task.Delay(1), which still seems arbitrary to me. I just sort of picked it after figuring out that Task.Yield() and Task.Delay(0) didn't work.
     
    gwelkind likes this.
  14. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    I completely agree.

    But also with:

    Code (csharp):
    1.  
    2. await UnityAsync.WaitForSeconds(1);
    3. await UnityAsync.WaitForSecondsRealtime(1);
    4. await UnityAsync.WaitForFrames(1); // -> yield return null;
    5.  
     
    gwelkind likes this.
  15. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,646
    Async/await generally allocates way more than coroutines... Did you actually measure that async/await is faster than coroutines or you're just guessing?

    Well, I would think that actually was the main idea of it. Compare these:

    Code (csharp):
    1. ThreadPool.QueueUserWorkItem(() =>
    2. {
    3.     DoSomething();
    4.     InvokeOnMainThread(() =>
    5.     {
    6.          Debug.Log("done something");
    7.          ThreadPool.QueueUserWorkItem(() =>
    8.          {
    9.              DoAnotherThing();
    10.              InvokeOnMainThread(() =>
    11.              {
    12.                    Debug.Log("Done another thing");
    13.              });
    14.          });
    15.     });
    16. });
    and

    Code (csharp):
    1. await DoSomethingAsync();
    2. Debug.Log("done something");
    3. await DoAnotherThingAsync();
    4. Debug.Log("Done another thing");
     
    Lorrak and rakkarage like this.
  16. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    it does make for cleaner coding :)
     
  17. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I don't want to be in charge of arguing that async would be useful in Unity since like I said, I'm still trying to figure out how it works. For example, this:

    If you put that into an async method and then call it with Task.Run, it does not actually return to the main thread in between await calls like implied; it throws an exception if you try to access anything Unity-related. From what I've read, there is no guarantee that await will return on any particular thread unless you set up a synchronization context, and calling Task.Run forces it to spin a new thread and switch to that.

    Anyway, it sounds like Unity's official stance for how to use async is "Don't". That we should only use it for doing non-Unity background processes on separate threads? And IEnumerator coroutines are here to stay as the main way to do asynchronous code in Unity?
     
    rakkarage likes this.
  18. bdominguezvw

    bdominguezvw

    Joined:
    Dec 4, 2013
    Posts:
    96
    I think that Unity should offer an alternative to Coroutines... And having .NET 4.6 is an opportunity to do that.

    Coroutines are something that it's really tied to Unity (specifically to MonoBehaviours and it's state) and really "weird".
     
  19. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,646
    I wouldn't put it as far as "don't". But async/await does not directly replace coroutines.
     
    Lorrak likes this.
  20. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    But are you talking internally about all this? We need to take advantage of having .net 4.6 and c#6.
     
  21. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,646
    About what exactly? It just sounds like you want to use async/await just because it's new and sounds cool, rather than because it has actual advantages. There wasn't a single good argument in this thread why coroutines should start using this mechanism.
     
    Lorrak likes this.
  22. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,042
    Coroutines depends on executing on the context of a Monobehaviour you can't execute it without one and also depends on it's state, it needs to be enabled in hierarchy.

    I'm not speaking of async/await because it's new and cool as you said.
     
  23. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I understand you've got some strong opinions Tautvydas but I'd really like to hear from someone else at Unity. Someone over there went through all the trouble of writing the SyncContext and figuring out how to track async tasks, and I kinda feel like whoever that was, they didn't expect someone to just show up, call everyone names and tell us all to screw off and stop asking about async.

    How about I'll agree with you that anyone who uses async is a horrible noob who doesn't deserve to have their code compile, if you can agree that since someone over there implemented this feature, the community could benefit from at least a short example of how it works. Even if it's just so that the good developers can use it to prove to all us plebs that async sucks and coroutines rule or whatever.
     
  24. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,646
    That's a very good point. Async/await is not tied to MonoBehaviour lifecycle (Update/LateUpdate/FixedUpdate), unlike coroutines.

    Come on now, I didn't call you names :). I'll ask the person who implemented the sync context to chime in.
     
  25. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,589
    I think it's really useful that async await is able to return a Task<TResult> that contains the result of the operation (example). Returning custom data with Coroutines doesn't seem as easy as with await.
     
    NGC6543 likes this.
  26. amit1532

    amit1532

    Joined:
    Jul 19, 2010
    Posts:
    305
    I have recently implemented what you guys ask for, async await with coroutines.

    You can return values, use WaitForSeconds (and more), use try catch and much more. On one thread.

    Ill be uploading it in the assets store for free and it will be open source
     
    NGC6543 likes this.
  27. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    I think that until Unity updates the API to better support async/await usage, there's not much point in replacing coroutines just yet. After all, async/await still runs synchronously (like coroutines) up until you actually create a task that needs to run on the thread pool. Then you need to be careful not to try to access the Unity API until you return to the main thread context.

    This is one case where it can be better to use coroutines -- if you want something to run on another thread, it has to be done deliberately, so keeping Unity API calls out of these functions can be easier or more obvious.

    All that said, I do prefer to use async/await because of the following reasons:

    1) If you're going to use async/await, then it's best to use it all the way down for your own sanity's sake. I don't want to use a mix of coroutines and awaitables as that can make program execution harder to follow. Plus, third party libraries are likely to use the async/await pattern in its interface. Plus, this is the direction .Net is heading, so I'm following that direction as well to keep using its features.

    2) It really does allow me to write cleaner code. State machines are simple to write, I can chain together task results, and less boilerplate code is needed to set up workers, etc. The TPL is a powerful library that only keeps getting better over time.

    3) Tasks can return more information than coroutines (as noted by @Peter77). Sure you can use callbacks in coroutines, but they can get awkward. You can also pass in execution conditions (e.g. break out of a chained task list on errors, etc.)

    4) As noted by @N3uRo, coroutines can unexpectedly die because they're tied to some MonoBehaviour. With async/await, I feel more confident that I fully control the flow.

    5) Coroutines are Unity only. async/await is not. This might or might not matter to you from a cross platform (engine?) perspective. You could roll your own coroutine library if you needed to in order to migrate a huge project, however. Although if you're planning on creating a 3rd party library that's engine agnostic, then async/await makes more sense.

    Ultimately, though, I think this boils down to programmer preference. IMO, if you're comfortable with coroutines and Unity for your existing project, keep using that and save async/await for a new project.
     
    Bezoro and TextusGames like this.
  28. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I don't know, I still feel like async/await was specifically designed by Microsoft to be useful completely separate from the ThreadPool. That's why it has so many different options for task flow and why it's not just directly tied to the thread pool. The idea of it, in my opinion, is to allow a formal way of writing ANY sort of asynchronous code, without caring about whether or not it's using threads under the hood. Code that needs to run over several frames in a game engine is a very common form of asynchronous task and it seems like an ideal fit for async/await, regardless of the fact that in Unity's case it would have to use time slicing instead of a hardware thread. Like I said, I didn't originally start this thread to come in and lecture about why async should be used; I came in thinking that Unity was already in agreement since they went through all the trouble of adding async support, and was hoping they could explain how it should best be used. Coroutines are just a huge hack; IEnumerator was not designed as a way to run asynchronous code; it was designed to enumerate a list of objects. I'll grant, it's a pretty clever hack, but it's still a hack that goes against the natural usage of the language. Async just makes more sense. And it will be much easier to adapt as pieces of Unity become more thread-safe.
     
  29. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    async/await is great, but it's also a crutch with a lot of gotchas and pitfalls. There are cases where deadlocks are possible (a lot of them actually). This happens more often in web based scenarios where you can't always guarantee the thread that the completion context will run on (so if you do something silly like MyAsyncMethod().Result then it can deadlock.

    async/await is great but has a lot of context issues due to automatic performance tuning and inlining and if you're not careful it can result in unexpected and hard to track down behavior, deadlocks, etc. Continuations can be tough and threads can become re-entrant. For example, if you're using a task with continuations that is called from more than one method... those continuations are executed in a serial fashion after the task is complete so it's possible to unexpectedly block a continuation while it's waiting for other continuations to complete.

    It does make for some cleaner code. I don't see it as a replacement for co-routines. There's nothing wrong with using them in conjunction with eachtoher... launch a co-routine that awaits a result from an async method, then does it's thing. Use the coroutine itself to work with the Unity objects. Take a look at the Spatial Understanding bits of the HoloLens Toolkit... they use state objects (structs) that are populated from separate threads, then when the async work is done, a component does the work of constructing the Unity objects such as mesh filters.
     
    rakkarage likes this.
  30. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Can't you set the completion context of a task? I thought that was the point of the Unity synchronization context, for example.

    Does it? I don't actually know, but I thought the automatic performance tuning stuff was mostly in Task.Run(), where it would automatically determine whether to use a thread or time slicing, etc. The actual async/await keywords themselves are pretty well-defined from what I understand and behave in a consistent way, it's just Task.Run that does all the black magic.

    I feel like that's true for any sort of asynchronous coding, even Coroutines. They're re-entrant and can block one another and deadlock if you have them improperly waiting on one another.

    But do coroutines offer anything better than async other than "we know how to use them"? I don't think there's anything better about using Enuermator/yield over async/await... I'd say it only looks simpler because we're more used to it. When I started using Unity, it looked completely bizarre to see Enumerators and yields that had nothing to do with enumerating or yielding; it was only after experience that I began to associate them with asynchronous code. That's not what they were designed for. Again, I'm not suggesting we use Task.Run() necessarily, in my examples above I never use Task.Run, I just start the task directly and call await Task.Delay(1) to yield... I guess I don't see why Enumerator/yield would be better.
     
    rakkarage likes this.
  31. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Yeah, async/await by themselves are synchronous. You need to actually create a task via Task.Run or StartNew to get it to run on the thread pool.

    Actually, this reminded me of your earlier question about Task.Yield(). From the MSDN docs:

    That may explain the issues you may be having with it. Honestly, Task.Delay(1) is probably the safest one to use if you just want to bail out of a function and continue the rest later on. (If you disassembled the Delay() function, you'll see that Task.Delay(0) just returns a CompletedTask, which basically turns that line into a no-op)

    I remember when I first started using coroutines, one thing I'd forget to do is to explicitly call StartCoroutine(), like so:

    Code (csharp):
    1. yield return DoSomethingAsync()
    instead of

    Code (csharp):
    1. yield return StartCoroutine(DoSomethingAsync())
    At least async/await can sometimes catch errors at compile time. Sometimes. :p
     
  32. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Yes, however using await doesn't guarantee that it'll run on the same thread. This is an example of why task based asyncronous programming is good for MVC applications. It returns the main thread to the thread pool and executes the async code (even when awaited) in a separate thread... then gives you a thread back when it finishes... but it's not guaranteed to be the same one.

    Now, async and await are not always used in conjunction. You don't always have to await the return of an async method... it returns a Task that you can get the result from at a later point in time. Calling .Result will force it to execute essentially syncronously and block the main thread until the async method returns.

    Now... here's what seems to cause confusion about async/await... using the async keyword does not actually create new threads, but it does execute on separate threads. It just uses an available thread from the thread pool. The execution of tasks are handled by the CLR from the thread pool based on the current available threads... so yes it is asynchronous and not synchronous.

    Async / Await is a bit different than the TPL... it's kind of syntactic sugar for the ContinueWith bits of Task but not fully featured.

    I would really recommend reading the following... including the comments as it'll give you a lot of insight into how it works and even show how issues can be created with async/await.

    https://github.com/dotnet/corefx/issues/2454
     
    rakkarage likes this.
  33. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    All async and await (the keywords) do is turn the code into a state machine. Also, "thread" is the incorrect word to use here. The important word is "context". The context is captured whenever you await a task, and is restored before continuing beyond the awaitable. That's how you can update the UI around long running tasks.

    See this MSDN link: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async

    And also this link: https://blog.stephencleary.com/2012/02/async-and-await.html

    And finally, a FAQ: https://blogs.msdn.microsoft.com/pfxteam/2012/04/12/asyncawait-faq/

    I'd like to point you to this question in the FAQ:


    Calling .Result is a bad idea in general, anyway. Just await the task. Or add the task to a queue, then await them all at once.

    Simply creating an async method does not make it execute on the thread pool. It returns a Task object, and that's it.

    When you await a task, then the following happens:

    1) First the task is checked if it's completed. If so, then execution continues. The thread pool is not involved.
    2) If the task is not completed, then the context is captured and is restored at a later time so the remaining code can continue on the same context.

    I'm not quite sure what I'm reading in that link. Yes, problems can arise if you use locks and try to use the same lock in another thread, causing a deadlock. That's not unique to tasks. Reentrancy is a problem in general in asynchronous programming, and people will need to educate themselves on how to avoid these pitfalls. It doesn't matter if it's done using threads, processes, async/await, coroutines, etc.

    Task continuation, chaining, etc. are advanced topics and I'd encourage people to first get comfortable with the practices of coding with multiple threads and concurrency. However, this is beyond Unity's scope as none of its API is designed for this (yet).
     
    rakkarage likes this.
  34. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I'd still like to hear from Unity about how the synchronization context works and how they expect it to be used. I remember reading somewhere that awaiting was supposed to return in the "Update" stage of the frame, but looking at the Profiler, it seems like await causes the async methods to return during FixedUpdate, which would seem to mean that if you something like "while(true){ DoThings(); await Task.Delay(1); }" it will only get called every FixedUpdate step (0.02 seconds), unlike Coroutines which yield every Update step, aka as fast as the framerate allows.
     
  35. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Absolutely, but you can queue up the tasks and periodically check the IsCompleted property first, and/or use a ContinueWith to signal what happens after completion. Awaiting isn't always absolutely necessary. If you don't await it's going to be synchronous for sure. Supplying a continuation will cause the continuation to be executed on a thread pool thread, depending on context.

    That actually depends on what's happening inside your async method... whether it has async stuff to do. In most cases, it's going to be calculations, etc and, while it might be parallel when it executes, it will always be synchronous. However, if it has async code itself, such as HTTP request, File stuff... etc... then it will use another thread... but it doesn't create one it just temporarily borrows a thread from the thread pool for the IO execution and returns it. Also, using ConfigureAwait(false) will execute the task on the thread pool as it doesn't capture context.

    My point is, if you really want stuff to run on a separate thread, async/await isn't the way to go about it. You should use the TPL and Task.Run, etc.
     
    Last edited: May 25, 2017
  36. joncham

    joncham

    Unity Technologies

    Joined:
    Dec 1, 2011
    Posts:
    276
    Hello,

    We added the sync context to support the most basic use case of running a Task on a another thread and then resuming flow back on the main/UI thread.

    We spent a week looking at replacing Coroutines with async/await a few years ago when it first came out. My mind is a bit foggy, but the end result was that it's not a straight forward replacement.

    I expect we'll revisit this once things stabilize with Mono/.NET upgrade, but there are no plans now to replace (or augment) coroutines with async/await.

    Thanks,
    Jonathan
     
    Lorrak and rakkarage like this.
  37. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Any chance for more info on how it works? Going by the Profiler, it seems like await will return to the main thread in the FixedUpdate step. Does it check for awaiting tasks every FixedUpdate? Or is it less often? Will there will be a sync context that runs in the Update loop instead?
     
    Karsten likes this.
  38. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I'd recommend this explanation which might help you get your head around it...
    https://blog.stephencleary.com/2012/02/async-and-await.html
     
    BobFlame and rakkarage like this.