管理人の技術ブログです。Awaitableパターンの自前実装Async前回はawaitするためのコンパイラ要件について確認しました。コンパイラは独自型をawaitするための汎用性を残した展開を行います。ここでは実際にAwaitableな型を自前実装してみます。Awaitableパターンの実装Awaitable型まずawait演算子に渡すAwaitable型を作成します。ここでは、外部からデリゲートとして与えられたタスクの非同期実行や、そのタスクが完了しているかどうかの取得、完了時にコールバックしてほしい処理の登録などを実装します。以下にそのサンプルを示します。完了したかどうかを取得します。指定の処理を非同期に実行します。完了時に実行する処理を登録します。対応するAwaiterを取得します。非同期処理の実行をラップします。}}awaitするためには、Awaitable型に対応するAwaiter型を返すGetAwaiterメソッドの実装が必要です。強調表示した箇所が今回の重要なポイントです。インスタンスメソッドで実装していますが、拡張メソッドでもOKです。Awaiter型Awaiterは、Awaitable型による非同期処理の完了待機を補助するためオブジェクトです。前回の示したコンパイラ要件に沿って実装しています。OnCompletedで登録される継続処理は、呼び出しスレッド上で動作するようにしています。完了したかどうかを取得します。継続処理を登録します。<remarks>継続処理は呼び出し元スレッドに同期的に実行するようスケジューリングします。}}利用してみるここまで作成してきた独自Awaitable/Awaiter型を実際に利用してみます。難しいことはなく、ボタンを押したら2秒後にランダムな値を返してそれを表示しているだけです。}}Awaitableパターンの改良ここまで、独自型をawaitさせるための実装を行ってきました。コンパクトに作ったつもりなのですが、そうは言っても例えば以下のようにアレコレ考慮しなければならないことがあって、とっても面倒くさいです。独自型によるawaitがどれほどニーズがあるのかは分かりませんが、どうせ作るならもっと楽をしたいところです。いうことで、以下のように改良してみました。指定の処理を非同期に実行します。対応するAwaiterを取得します。非同期処理の実行をラップします。}}TaskCompletionSource<T>とTaskAwaiter<T>を利用しているのが、今回のポイントです。TaskCompletionSource<T>は内部にTask<T>を持っており、TaskCompletionSource<T>を経由することで外部から任意のタイミングで結果を与えることができます。上のサンプルの28、29行目が外部から結果を与えているところです。通常のTask<T>は、コンストラクタにデリゲートとして与えたタスクを実行させ、その完了に基づいて戻り値や例外などの結果を取得する、という使い方をします。このように、Task<T>は一度インスタンスを作ってしまうと後は外部から結果を取得するだけになってしまいますが、TaskCompletionSource<T>を利用すればそれを上手く解決できます。対応するAwaiterもイチイチ実装することはなく、TaskCompletionSource<T>が持っているTask<T>のGetAwaiterメソッドを利用するだけでOKです。このように、Task/TaskCompletionSourceは非常に汎用的で強力です。awaitの独自実装をすることがあれば、その内部でこれらを利用すると手間が軽減して良いと思います。(1)月別アーカイブはてなブログをはじめよう!xin9leさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。