async/await とは
- C# の機能として用意されている
- C# 5.0の新機能
- Unity 2018 から C# 6.0 に対応した(それまでは C# 4.0 でした。)
- Task クラスに対して使うものという軽い認識(本当は
INotifyCompletion
インターフェースのIsCompleted
とGetResult
)
- Unity で
.NET 4.x
を選択すれば使用可能 - async = asynchronous = 非同期
- await を使うメソッドにつけなければならないキーワード
- async をつけただけでは普通のメソッドと挙動は変わらない
- await
- 非同期メソッドを呼び出し、完了するまで実行を中断するキーワード
- 戻り値を取得できる(コルーチンは無理)
- 並列処理だけど、普通のメソッドの呼び出しと同じようにかける = 可読性が上がる
基本的な構文は、
async 戻り値の型 メソッド名(引数)
{
await ~~~~
}
戻り値は、基本的に Task / Task<T>
型と考えて問題ないかと思います。
(といいながら、サンプルは void
で書いたりしています…)
機能
Task.Delay(Int32 millisecondsDelay)
引数で指定されたミリ秒待機します。
using System.Threading.Tasks;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
AsyncSample1();
}
async void AsyncSample1()
{
Debug.Log("AsyncSample1 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample1 End.");
}
}
今回のサンプルでは、1000ミリ秒 = 1秒 待機するようにしたので、実行結果は以下のようになります。
ContinueWith
直列処理できます。
非同期処理のメソッドも、ContinueWith
でつなぐことで、直列化することができます。
ちなみに ContinueWith
を使わずに、普通に並列的に処理にしたければ、下のようになイメージになります。
using System.Threading.Tasks;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
AsyncSample1();
AsyncSample2();
}
async void AsyncSample1()
{
Debug.Log("AsyncSample1 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample1 End.");
}
async void AsyncSample2()
{
Debug.Log("AsyncSample2 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample2 End.");
}
}
並列なので、
AsyncSample1 Start.
AsyncSample2 Start.
AsyncSample1 End.
-
AsyncSample2 End.
という結果になります。
ContinueWith
を使って直列処理(AsyncSample1 を実行完了後、 AsyncSample2 を実行)するサンプルは、
using System.Threading.Tasks;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
AsyncSample1().ContinueWith(_ => AsyncSample2());
}
async Task AsyncSample1()
{
Debug.Log("AsyncSample1 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample1 End.");
}
async Task AsyncSample2()
{
Debug.Log("AsyncSample2 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample2 End.");
}
}
注意点は、メソッドの返り値の型が Task になっていることくらいです。
AsyncSample1 Start.
AsyncSample1 End.
AsyncSample2 Start.
-
AsyncSample2 End.
という結果になります。
Task.Run()
引数として与えられた処理を別スレッドで実行します。
用途として、重い処理を非同期にしたいときに使います。
ちょっと良い例が思いつかないので、超簡単なサンプルだけ書きます。
Task.Run(() => Debug.Log("重い処理..."));
Task.WhenAll()
指定された Task が全て完了してから Task を実行することができます。
using System.Threading.Tasks;
using UnityEngine;
public class Test : MonoBehaviour
{
async void Start()
{
await Task.WhenAll(AsyncSample1(), AsyncSample2());
Debug.Log("All Completed.");
}
async Task AsyncSample1()
{
Debug.Log("AsyncSample1 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample1 End.");
}
async Task AsyncSample2()
{
Debug.Log("AsyncSample2 Start.");
await Task.Delay(1000);
Debug.Log("AsyncSample2 End.");
}
}
AsyncSample1()
, AsyncSample2()
の実行が完了したら、 All Completed.
を出力するようなサンプルです。
実行結果は、
Task<T>
戻り値が欲しい場合は Task<T>
を使います。
using System.Threading.Tasks;
using UnityEngine;
public class Test : MonoBehaviour
{
async void Start()
{
var str = await AsyncSample1();
Debug.Log(str);
}
async Task<string> AsyncSample1()
{
Debug.Log("AsyncSample1 Start.");
await Task.Delay(1000);
return "AsyncSample1 End.";
}
}
AsyncSample1()
の結果が返ってくるまで、Debug.Log
されると困るので、 await
する必要があります。
(待ち受けするイメージです。)
結果は、
終わりに
Unity 初心者なので、間違いがあったら教えてくれるとありがたいです。
Unity で Task 使うなら、 UniTask 使おう。(理由はいまいち知らない…)
理由は UniTask の記事を書くときに、調べたいと思います。
参考文献
https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task?view=netcore-3.1
https://torikasyu.com/?p=1554
https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task.whenall?view=netcore-3.1
https://www.slideshare.net/UnityTechnologiesJapan/unite-tokyo-2018asyncawait