サンプルは「呼び出すたびに開始と再開を繰り返すコルーチン的なもの」です。ボタンのイベントハンドラに割り当てると楽しいかもしれません。
前にも EnumerableEx.Create
を作るためにawaitableなクラスを作りましたが、その時と比べると随分シンプルです。
public interface IAwaitable : ICriticalNotifyCompletion { IAwaitable GetAwaiter(); bool IsCompleted { get; } void GetResult(); } public class YieldState : IAwaitable { private Action _continuation; #region IAwaitable IAwaitable IAwaitable.GetAwaiter() => this; bool IAwaitable.IsCompleted => false; void IAwaitable.GetResult() { } [System.Security.SecurityCritical] void ICriticalNotifyCompletion.UnsafeOnCompleted(Action continuation) { _continuation = continuation; } void INotifyCompletion.OnCompleted(Action continuation) { _continuation = continuation; } #endregion public IAwaitable Yield() => this; public bool TryResume() { if (_continuation != null) { var cont = _continuation; _continuation = null; cont(); return true; } else return false; } }
で、こんな感じに使います。
void Main() { for (int i = 0; i < 6; ++i) Foo(); } YieldState fooState = new YieldState(); async void Foo() { if (fooState.TryResume()) return; for (int i = 0; i < 2; ++i) { Console.WriteLine(i); await fooState.Yield(); } Console.WriteLine("done."); }
この実装は同期コンテキストの調整をしていませんから、TryResume()
したコンテキストで続きが実行されることになります。まあイベントハンドラみたいな場面では特に問題ないですね。
例外処理もイベントハンドラがTaskを返せないので無視していますが、async Task
に変えたうえでawaitなりContinueWithすれば処理することができます。再開した場所で捕捉したい場合は・・・前回の MoveNext()
のように、Taskに格納された例外を投げなおすような実装が必要になります。
ちなみに、別の場所で立てるフラグを非同期に待ちたいだけなら、こんなことをせずとも TaskCompletionSource
を使えばいいです。