Asynchronous Programming in Unity C# (Async/Await)
We have to write many time-consuming codes like calling web API, playing animation, or calculating the optimized path for a destination. Here comes the asynchronous programming. As Unity runs in the main thread so these types of time-consuming tasks will freeze your game which is bad for your game performance.
In C# we can do this by using Coroutine or Async/Await. Many developers think that Async/Await is the replacement for Coroutine. But that is not true. We still need Coroutine. That is another debate. Both have some benefits and drawbacks. Here I will discuss how we can use Async/Await in Unity. Let’s jump into Unity.
Scenario — At first I will try to execute some time-consuming tasks in Unity using a typical synchronous way. Then do the same thing asynchronously in both normally and parallelly using async. Then compare the execution time and performance for all of them.
private float MyLongRunningJob(float token)
{
// Debug.Log("U>> Runn " + token);
// Do something that takes times like a Thread.Sleep in .NET Core 2.
Thread.Sleep((int)token);
return token;
}
The above function is a long-running task. This function will sleep/hold the execution for a certain amount of time. Now we will call this function several times synchronously and asynchronously.
Setup Tasks —
List<float> items = new List<float>();
public void Start()
{
Debug.Log("Start");
items.Add(2001.0f);
items.Add(1002.0f);
items.Add(3003.0f);
items.Add(10004.0f);
items.Add(10005.0f);
}
Here I initialize a float-type array. This list of floats is the amount of sleeping/execution time for each task. So the total execution time is 2001 + 1002 + 3003 +10004 + 10005 = 26015 Ms(Mili Seconds) or 26 Second.
Synchronous execution —
public void ExecuteSyncOperation()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
LongRunningOperationNonSync();
watch.Stop();
var elapsedTime = watch.ElapsedMilliseconds;
resultText.text = resultText.text + " Total Time : " + elapsedTime;
}
private void LongRunningOperationNonSync()
{
for (int i = 0; i < items.Count; i++)
{
MyLongRunningJob(items[i]);
}
}
in the LongRunningOperationNonSync function, I just call our MyLongRunningJob function several times. And in the ExecuteSyncOperation function just call the LongRunningOperationNonSync function. Here I implement a stopwatch to calculate the execution time. The whole code snippet is self-explanatory. Now, let's run it into unity and see the result.
As you see it will take 26022 Milliseconds. Our expected time was 26015 Ms. So it makes sense. But the worst part is when you run this operation your entire app will freeze which is bad for your app/game performance. Now let's do this asynchronously.
Asynchronous execution — Now we will do it asynchronously using async.
public async void ExecuteAsyncOperation()
{
resultText.text = "";
var watch = System.Diagnostics.Stopwatch.StartNew();
await AsyncOperation();
watch.Stop();
var elapsedTime = watch.ElapsedMilliseconds;
resultText.text = resultText.text + " Total Time : " + elapsedTime;
}
private async Task AsyncOperation()
{
for (int i = 0; i < items.Count; i++)
{
// Do something that takes times like a Thread.Sleep in .NET Core 2.
await Task.Run(() => MyLongRunningJob(items[i]));
}
}
AsyncOperation Function — This is our async task function. You have to put async during function declaration to mark this as an asynchronous function. If you do not return anything then you have to return Task. You can also return from this function. In that case, you have to write Task<return Type> (example: Task<float>). Inside the loop, we run our task using lambda expression and wait for the competition of that task by using await.
So to run a function asynchronously you have to do the following thing:
1. Write async during the declaration of the function to mark it asynchronous.
2. Use the return type Task if you do not return anything. If you want to return anything then use return type Task<return type>
3. Declare the task and wait for the completion of that task like below
await Task.Run( () => MyFunction () );
ExecuteAsyncOperation — In this function we just call the AsyncOperation function. We use await for waiting the task completion. As we are using await so we also have to put async before this function also.
This asynchronous function took 26044 Ms to execute which is almost the same as synchronous. But the main difference is in performance. In asynchronous, time-consuming tasks are performed in another thread, so this time your app will not freeze and it will increase your game performance a lot.
Parallel Asynchronous —
public async void ExecuteParallelAsyncOperation()
{
resultText.text = "";
var watch = System.Diagnostics.Stopwatch.StartNew();
await RunParallelAsync();
watch.Stop();
var elapsedTime = watch.ElapsedMilliseconds;
resultText.text = resultText.text + " Total Time : " + elapsedTime;
}
private async Task RunParallelAsync()
{
List<Task<float>> tasks = new List<Task<float>>();
foreach (var item in items)
{
tasks.Add(Task.Run(() => MyLongRunningJob(item)));
}
var results = await Task.WhenAll(tasks);
foreach (var item in results)
{
Debug.Log("U>> Completed task .. " + item);
}
}
Here the ExecuteParallelAsyncOperation function is the same as the ExecuteAsyncOperation function.
RunParallelAsync Function — Here I create a task array of type float. Then I append the task array. And finally waiting for all of the tasks completion.
In this case, the execution time is 10033 Ms which is less than half of the previous operations. Basically, parallel asynchronous operations run a couple of tasks together parallelly in different threads. It depends on the device's processing power and the CPU core.
Though the parallel asynchronous task is very fast, it is tougher to maintain and needs good knowledge to handle it properly. So before using this please consider the use case and how it affects your application. This does not mean that it is rocket science and you should not use parallel asynchronous operations in your application. Just use it when you need it and do not use it blindly.
I hope this will help you to understand basic asynchronous programming in C# and Unity. I will write another article about, how to get the progress result from an asynchronous task and how to cancel the task. You get the full project from this Github repository.