イベントハンドラのasync

さらに先日書いた記事の補足。


実行されないコードの問題はawait付きの呼び出しではなく、async付きのイベントハンドラのようだ。

private async void OnClick(object sender, EventArgs args)
{
  Debug.WriteLine("hoge1");
  await hoge2();
  Debug.WriteLine("hoge3");
}

みたいなイベントハンドラがOnSuspendingを起点とするコードパスで呼び出されると、await以降のコードの実行は保証されない。


イベントハンドラを同期的に実行できないかといろいろ試してみた結果、組み込みのイベントはどうにもできないらしい。
もちろんイベントハンドラの中と外で同期オブジェクトでも使えば何とでもなるとは思うが、そういうことではなく。
独自にイベントを定義すればasync/awaitで待機することはできる。

public delegate Task AwaitableEventHandler(object sender, EventArgs args);
public event AwaitableEventHandler Event;

protected async Task OnEvent(object sender, EventArgs args)
{
  await RaiseEventAsync(Event, sender, args);
  // await Event(sender, args); これだと複数のイベントハンドラが設定されている時に1つが終わったらそこで待機から戻ってしまう
}

protected async Task RaiseEventAsync(Delegate handler, params object[] parameters)
{
  await Task.WhenAll(handler.GetInvocationList().Select(d => (Task)d.DynamicInvoke(parameters)));
}

みたいな感じか。


awaitを使わないハンドラでも何らかのTaskオブジェクトを戻すようにしなきゃならないとか、いまいち使い勝手が悪い気もするが。