Movatterモバイル変換


[0]ホーム

URL:


Sitemap
Open in app

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:

  • The use of coroutines (andyieldstatements) make handling asynchronous calls (usingWWW) flow like synchronous code.
  • However, because one cannot put ayieldinside a try-catch block, we have to create a complicated mix of asynchronous (inspectingwww.error) and synchronous (try-catch) error handlers.
  • Because coroutines cannot return values, we have to create a huge monolithic coroutine (FetchData).
  • We are required to stack the requests; i.e., fetchingusers completes and then fetchingtodos starts.

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:

  • Becauseasyncmethods return data, we break out the code for fetching users and todos;FetchUsersandFetchTodos.
  • In addition to returning data,asyncmethods return errors through the returned tasks. This allows one to centralize error-handling through a top-leveltry-catch block.
  • Like the previous example, this example stacks fetching the data.

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).

--

--

John Tucker
John Tucker

Written by John Tucker

Broad infrastructure, development, and soft-skill background

Responses (3)


[8]ページ先頭

©2009-2025 Movatter.jp