You’ve described the synchronous approach, where you call something and freeze until that something completes. The async stuff in Unity Runtime Upgrade Preview and my async stuff for the current versions of Unity work different, just like in WinForms and WPF applications.
For example, you have a button on the screen and a corresponding OnClick handler in the code:
public async void OnClick()
{
button.interactable = false; // runs on the main thread right away
await Task.Factory.StartNew(DoStuff);
button.interactable = true; // will run on the main thread in some distant future
}
By the way, you can’t use coroutines as direct event handlers, but async methods fit here perfectly.
The user presses the button and OnClick() gets called. The method deactivates the button, launches the task, that is supposed to be doing some CPU- or IO-heavy stuff, and immediately returns. No freezing, no blocking the main thread. The button is inactive, the heavy stuff is running somewhere (in the thread pool by default), the main thread is also running and doing what it usually does.
In a second or in an hour or after ten millions of frames, when the heavy calculation finally completes, the continuation (the rest of the method, everything after the await line) will to be executed. This will happen on the main thread in a way not much different from how Unity runs different sections of coroutines that spread their work over multiple frames.
Basically, the coroutines and async/await look and feel very similar. Coroutines in Unity are a poor-man’s way to do asynchronous calls, invented at the times when the feature wasn’t directly supported by the language.
Async/await requires a bit less boilerplate code, async methods can return results, and await is easily extensible. You can await any thing, if that thing has a corresponding awaiter. For example, you can write an awaiter for Func to await some arbitrary condition to become true in one line. I wrote this example yesterday just for fun. There is room for improvement, but as a proof of concept works fine.
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public static class Until
{
public static ConditionAwaiter GetAwaiter(this Func<bool> condition) => new ConditionAwaiter(condition);
public static ConditionAwaiter IsTrue(Func<bool> condition) => new ConditionAwaiter(condition);
private static async void UsageExamples()
{
await (Func<bool>)(() => DateTime.Now.Second == 0);
await new Func<bool>(() => DateTime.Now.Second == 0);
await Until.IsTrue(() => DateTime.Now.Second == 0);
}
}
public class ConditionAwaiter : INotifyCompletion
{
private readonly Func<bool> condition;
private readonly SynchronizationContext context;
public ConditionAwaiter(Func<bool> condition)
{
this.condition = condition;
context = SynchronizationContext.Current;
}
public ConditionAwaiter GetAwaiter() => this;
public void GetResult() { }
public bool IsCompleted => condition();
public void OnCompleted(Action action)
{
Task.Factory.StartNew(() =>
{
while (condition() == false)
{
Thread.Sleep(16);
}
if (context != null)
{
context.Post(state => action(), null);
}
else
{
action();
}
}, TaskCreationOptions.LongRunning);
}
}