Usingasync / await / Tasks greatly simplifies writing asynchronous code inUnity.
In this article, the examples are all focused on fetching data from two REST API endpoints (usersandtodos); sample APIs provided byJSONPlaceholder.
The examples all useUnity’sWWWutility module to retrieve the JSON data andJsonUtility(and a smallhelperclass) to parse it into an array of classes.
Once the data from both endpoints are fetched and parsed, the examples do something with both arrays of classes; in this case simply output the data usingDebug.Log.
The variation between the examples comes in the form of how the asynchronous code is organized.
Coroutine
Let us first see how we would implement fetchingusersandtodosusing the familiar approach; coroutines.
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame.
— Unity Documentation —Coroutines
using System.Collections;
using System.Linq;
using UnityEngine;public class DataController : MonoBehaviour
{
readonly string USERS_URL = "https://jsonplaceholder.typicode.com/users";
readonly string TODOS_URL = "https://jsonplaceholder.typicode.com/todos";IEnumerator FetchData()
{
Todo[] todos;
User[] users; // USERS
var www = new WWW(USERS_URL);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log("An error occurred");
yield break;
}
var json =www.text;
try
{
var userRaws = JsonHelper.getJsonArray<UserRaw>(json);
users = userRaws.Select(userRaw => new User(userRaw)).ToArray();
}
catch
{
Debug.Log("An error occurred");
yield break;
} // TODOS
www = new WWW(TODOS_URL);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log("An error occurred");
yield break;
}
json =www.text;
try
{
var todoRaws = JsonHelper.getJsonArray<TodoRaw>(json);
todos = todoRaws.Select(todoRaw => new Todo(todoRaw)).ToArray();
}
catch
{
Debug.Log("An error occurred");
yield break;
} // OUTPUT
foreach (User user in users)
{
Debug.Log(user.Name);
} foreach (Todo todo in todos)
{
Debug.Log(todo.Title);
}
} void Start()
{
StartCoroutine(FetchData());
}
}
Observations:
Async / Await / Tasks
This approach draws heavily from the articleAsync-Await instead of coroutines in Unity 2017.
To enable this approach, one needs to change the project’s scripting runtime version using the menu choices (Unity 2018):
Edit > Project Settings > Player > Configuration > Scripting Runtime Version > .NET 4.x Equivalent
Also, one needs to install a plugin using:
Asset Store > Async Await Support
Much like coroutines and theyieldstatement,asyncmethods and theawait statement allow methods to be paused (waiting for result from an asynchronous call) and then resumed. The key difference, however, isasyncmethods can return data.
note: If you have experience with modernJavaScript, this approach is just likeasync / await / Promises; whereTasks are just likePromises.
using System;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;public class DataAsyncController : MonoBehaviour
{
readonly string USERS_URL = "https://jsonplaceholder.typicode.com/users";
readonly string TODOS_URL = "https://jsonplaceholder.typicode.com/todos"; async Task<User[]> FetchUsers()
{
var www = await new WWW(USERS_URL);
if (!string.IsNullOrEmpty(www.error))
{
throw new Exception();
}
var json =www.text;
var userRaws = JsonHelper.getJsonArray<UserRaw>(json);
return userRaws.Select(userRaw => new User(userRaw)).ToArray();
} async Task<Todo[]> FetchTodos()
{
var www = await new WWW(TODOS_URL);
if (!string.IsNullOrEmpty(www.error))
{
throw new Exception();
}
var json =www.text;
var todosRaws = JsonHelper.getJsonArray<TodoRaw>(json);
return todosRaws.Select(todoRaw => new Todo(todoRaw)).ToArray();
} async void Start()
{
try
{
var users = await FetchUsers();
var todos = await FetchTodos(); foreach (User user in users)
{
Debug.Log(user.Name);
} foreach (Todo todo in todos)
{
Debug.Log(todo.Title);
}
}
catch
{
Debug.Log("An error occurred");
}
}
}
Observations:
Task.WhenAll
TheTaskclass has some utility methods for managingTasks; in particularWhenAllthat returns a newTaskwhen all of the tasks in the provided array ofTaskscomplete.
A simple change in the previous code enables fetchingusersandtodosto happen simultaneously.
...
try
{
var usersTask = FetchUsers();
var todosTask = FetchTodos();
await Task.WhenAll(usersTask, todosTask);
var users = await usersTask;
var todos = await todosTask;
...
Conclusion
Using the modernC#async / await / Taskfeatures greatly simplifies writing asynchronous code in Unity. And in particular, the approach is similar to modernJavaScript(making it particularly easy to learn — at least for me).
Broad infrastructure, development, and soft-skill background