Unityの機能をawait可能にしてasync / awaitについて学ぶ

143876 ワード

概要

C#では非同期処理として async / await の仕組みが用意されています。しかし実はこの機能、コンパイラによってコードが変更され、コールバックという形に変換されて動作するようになっているのです。そのため、知らずに使っていると思わぬところでハマったり、エラーになってしまったり、といったことが起きえます。

今回はそんな async / await の機能の内部を探りながら、最後はUnityの機能を拡張する実装を通して非同期処理を理解していこうと思います。

安原さんと名雪さんのスライドがとても分かりやすく参考になるので、こちらもぜひ読んでみてください。

非同期処理の挙動を確認する

細かい内容に入っていく前に、しっかりと把握していないとどういうことが起きるのかということを確認しておきましょう。

上のスライドにも書かれている以下のメソッドを実行するとどうなるでしょうか?

using System;
using System.Threading;
using System.Threading.Tasks;

public class App
{
    private static void Main(string[] args)
    {
        AsyncTest();
        Console.ReadLine();
    }

    private static async void AsyncTest()
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        
        await Task.Delay(1000);
        
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    }
}

ぱっと見た感じではどちらも同じIDが出力されそうです。では実際にこれを実行するとどうなるでしょうか。

自分の環境で実行してみると以下の結果になりました。

1
5

これは await Task.Delay(1000); の前後でスレッドが異なっていることを示しています。

さて、ではどうしてこういうことが起きるのか理由を探っていきましょう。

非同期メソッドを持ったクラスをデコンパイルして確認する

冒頭で書いたように、非同期処理はコンパイラがコールバックの形で動作するように変換してくれています。そのため、コンパイルされたものをデコンパイルすることでどういう変換がなされているか確認することができます。SharpLabというWebサービスを使ってどう変換されているのか見てみましょう。