Confusion with how async and await work

My code awaits a method which contains an await Task.Run but when I exit my program, that method keeps running, even after the code inside Task.Run. Does that mean for any method tagged with “async”, the entire method is running in a new thread? I previously thought only the stuff inside await is running inside a new thread.

There’s a warning message that comes up if a method is labeled async and doesn’t have await. It will say that the method will run synchronously. I thought that if I then add an await inside the async method, only the stuff inside “await” will be running inside a new thread. But according to the behavior in my code it seems like the whole method runs in a new thread so it won’t be able to exit when my main program exits.

If this is true, is there an easy way to have these async methods exit with the program exits (are cancellation tokens the only way)?

async and await can be compared loosely to how IEnumerables and the yield keyword works. Your code is no longer a linear list of instructions. In the case of the yield keyword all your code inside such a generator method gets torn apart and transformed into a statemachine object that represents your original code and you can “iterate” through the yielded values. Aysnc and await is much more obscure but work similarly. Though instead of having to iterate an IEnumerator manually (or in case of Unity’s coroutine scheduler, it’s doing the iteration for you), async also comes with the concept of execution contexts which are responsible for continuing a task once finished. If an async method can not be completed synchronously it would signal that through its Tasks IsCompleted property. If that is false, the rest of your code is essentially passed as a delegate to the synchronisation context for later processing. This is where it gets even more complicated. Those contexts can be set / exchanged for a certain task which would control where and when it actually is continued.

Here’s an article that tries to explain some of the background, though it’s just the top of the iceberg.

The actual behaviour of await and async depends on what kind of synchronisation context is actually setup. Unity actually implements its own synchronisation context in order to continue tasks on the main thread, just like the coroutine scheduler does. Here’s another article about async in Unity which goes a bit more in detail about the synchronisation context. Though the overall boilerplate and infrastructure of async / await is a lot more complex when compared to “classical” coroutines in Unity. Though they are also more flexible to some degree.

So to answer your question, I would say yes, a cancellation token is probably the only reliable way to cancel an asynchronous task as it is hard to know where which parts of an async - await chain are actually waiting to be continued.

Let me add that even I have just scratched the surface of that topic. It’s at least 10 times more complex than ordinary coroutines in Unity. I haven’t used async methods in Unity much and I currently don’t work on anything in Unity. Maybe one day I may dive a bit deeper and doing some tests, however at the moment I simply do not know enough to make clear statements about async methods in Unity. So you might want to get your own hands dirty :slight_smile: