📚

C#向けに async/await 対応の待機可能なコルーチンライブラリ AwaitableCoroutine を作ったから紹介

2022/04/11に公開

コルーチンについて

コルーチンはいったん処理を中断した後、続きから処理を再開できる。コルーチン - Wikipedia

ゲーム開発してると欲しくなることが多いですね。

C#では IEnumerator を利用して記述することが多いと思いますが、微妙な点として、次のような事が挙げられます。

  • IEnumeratorは遅延シーケンスのための機能であり、コルーチンを管理する仕組みは別途必要。
  • 必ずメソッドとして定義する必要がある。
  • 子のコルーチンを実行する際に、一々 foreach で記述する必要がある。
    • あるいは、管理する仕組みの方で頑張る

一方で Task を使おうとすると、マルチスレッドのための余計なオーバーヘッドがあったり、一時停止やキャンセルは少し面倒だったりします。

参考

AwaitableCoroutine

using System;
using AwaitableCoroutine;

// コルーチンを管理・実行するオブジェクト
var runner = new CoroutineRunner();

int count = 0;

// コルーチンを作成・登録する
var coroutine = runner.Create(async () => {
    Console.WriteLine("Started!");

    for (var i = 0; i < 10; i++)
    {
        count++;
        await Coroutine.Yield();
    }
}).OnCompleted(() => Console.WriteLine("Finished!"));

while (true)
{
    // runnerに登録されたコルーチンの処理を進める。
    runner.Update();
    
    // コルーチンが正常に完了していたらループを抜ける
    if (coroutine.IsCompletedSuccessfully) break;

    Console.WriteLine($"{count}");
}
  • CoroutineRunnerUpdateメソッドを呼び出すことで(明示的に)処理を進める。
  • コルーチンのキャンセルも簡単にできる。
  • 子のコルーチンは、コルーチンを呼び出して await するだけ。
  • asyncラムダ式でコルーチンを記述できる。
    • もちろん、通常のasyncメソッドでも記述できる。

嬉しいですね。

補足

async/awaitTask みたいな見た目ですが、AwaitableCoroutine はシングルスレッドのための非同期処理であって、マルチスレッドは絡まないです。 コルーチンの処理は CoroutineRunner.Update したときに実行されます。

注意点

Coroutine を作成する際にどの ICoroutineRunner に登録するか教えてあげる必要があります。呼び出し元で指定すれば、asyncメソッド内では自動的に伝播されます。

基本的にはCreate拡張メソッドを使用します。

private static async Coroutine FooCoroutine()
{
    // 伝播されるのでここで`Create`は必要ない
    await Coroutine.DelayCount(5);
    Console.WriteLine("Hello!");
}

var coroutine = runner.Create(FooCoroutine);

Coroutineのタプルを返すなど、CoroutineまたはCoroutine<T>以外の型を作成したい場合は、以下のようにContext拡張メソッドを利用します。

var (c1, c2) = runner.Context(() => {
    return (Coroutine.DelayCount(2), Coroutine.DelayCount(3));
);

コルーチンの作成に引数を与えたい場合は以下のようにします。

var coroutine = runner.Context(() => FooBarCoroutine(arg1, arg2));

補助メソッド

色々用意しています。

  • Yield
  • While
  • DelayCount
  • WaitAll
  • WaitAny
  • Select, SelectTo
  • AndThen
  • UntilCompleted
  • FromEnumerator
  • AwaitTask
  • AwaitObservable
  • AwaitObservableCompleted

詳しくはドキュメントを見てください。
AwaitableCoroutineドキュメント

その他のパッケージ

F#向けのパッケージと、ゲームエンジンAltseed2向けのパッケージを用意してあります。

AwaitableCoroutine.FSharp

F#向けのコンピューテーション式を提供しています。

let _ = runner.Create(fun () ->
  coroutine {
    printfn "1"
    do! Coroutine.Yield()
    printfn "2"
    yield ()
    printfn "3"
  }
)

let _ =
  runner.Do {
    let! _ = Coroutine.DelayCount(5)
    printfn "Hello"
  }

()

AwaitableCoroutine.Altseed2

ゲームエンジンAltseed2向けの拡張パッケージです。

ICoroutineRunnerを実装したCoroutineNodeと、DelaySecondコルーチンを提供しています。CoroutineNodeは登録するとOnUpdateでコルーチンを更新します。

var coroutineNode = new CoroutineNode();
Engine.AddNode(coroutineNode);

coroutineNode
    .Create(() => Altseed2Coroutine.DelaySecond(5.0f))
    .OnCompleted(() => Console.WriteLine("Finished!"));

インストール

NuGetからインストールできます。

PackageId Badge
AwaitableCoroutine AwaitableCoroutine - NuGet Gallery
AwaitableCoroutine.Altseed2 AwaitableCoroutine.Altseed2 - NuGet Gallery
AwaitableCoroutine.FSharp AwaitableCoroutine.FSharp - NuGet Gallery

実装について

Task-Like、Awaitable パターン、AsyncMethodBuilder を利用しています。

参考になりそうな記事

最後に

質問あればTwitterやGitHubで。

Discussion