おれんじりりぃぶろぐ

きっと何者にもなれないエンジニアのブログ

Unityコルーチンをawaitするサンプル

概略

C#5からawait句が導入され非同期実装がとてもお手軽にできるようになりました。 awaitはTaskへ使うものと思ってしまいがちですが、一定の条件を満たせばawaitを使った非同期実装を行うことが出来ます。

実装

AwaitHelloWorld.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

public class AwaitHelloWorld : MonoBehaviour
{

    [SerializeField] private GameObject go;
    private ControlCoroutine controlCoroutine;
    
    private async void Start()
    {
        int starttime = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                    DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        controlCoroutine = go.GetComponent<ControlCoroutine>();
        var result = new HelloWorldAwaitable(controlCoroutine);
        
        /*for (var i = 0; i < 1000; i++)
        {
            Debug.Log("HelloWorld");
        }*/
        
        int now = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                  DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        Debug.Log($"time:{now-starttime}");

        await result;
        Debug.Log(result);
    }

    public void ClickButton()
    {
        Debug.Log("On Clicked Button!!!");
    }
}

public class HelloWorldAwaitable
{
    private ControlCoroutine cc;
    public HelloWorldAwaitable(ControlCoroutine controlCoroutine)
    {
        this.cc = controlCoroutine;
    }
    
    public HelloWorldAwaiter GetAwaiter()
    {
        return new HelloWorldAwaiter(cc);
    }
}

public struct HelloWorldAwaiter : INotifyCompletion
{
    private ControlCoroutine cc;

    public HelloWorldAwaiter(ControlCoroutine controlCoroutine)
    {
        this.cc = controlCoroutine;
    }
    
    public bool IsCompleted
    {
        get { return false; }
    }

    public string GetResult()
    {
        return "OnCompleated!";
    }

    public void OnCompleted(Action continuation)
    {
        cc.RegisterCoroutine(() =>
        {
            continuation();
        });
        
    }
}

ControlCoroutine.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ControlCoroutine : MonoBehaviour 
{
    public void RegisterCoroutine(Action action)
    {
        StartCoroutine(WorkCoroutine(action));
    }

    private IEnumerator WorkCoroutine(Action action)
    {
        for (var i = 0; i < 1000; i++)
        {
            Debug.Log("HelloWorld");
            yield return null;
        }
        // callbackを実行する
        action();
    }
}

それぞれのソースコードをGameObjectにComponentとして追加します。

まとめ

けっこうコード量が増えパット見は面倒な感じになってしまいました。 しかしWorkCoroutineのところだけ、カスタマイズしてあとは共通のものと考えてしまえばそれほど冗長でもなさそうです。 コルーチンを連続して使うときなどはコールバック地獄からも開放されそうです。

参考