.NET 4.0以下において、TaskクラスはIDisposableインターフェイスを公開している。これは、IAsyncResultインターフェイスのAsyncWaitHandleプロパティによって公開されているwaitハンドルをクリーンナップするためだ。.NET 4.0において、このwaitハンドルはAsyncWaitHandleプロパティが読み出されたり、Task.WaitAllもしくはTask.WaitAnyが使われたときにだけ作られる。それ以外では、Task.Disposeを呼び出す必要はない。
残念なことに.NET 4.0では、Taskクラスは過度に例外ObjectDisposedExceptionを投げる。Disposeを呼び出すと、たとえリリースされたwaitハンドルを使うプロパティがなくても、オブジェクト全体が使えなくなるのだ。
.NET 4.0ではTask.Disposeを呼び出すべきだろうか?
答えはノーだ。ただし、以下が当てはあまらない限りはだ。
- 完了したTaskはキャッシュされない。
- waitがTask.WaitAllかTask.WaitAnyの呼び出し、もしくはIAsyncResult.AsyncWaitHandleの読み出しにより作られた。
- 対象となるTaskを保持するタスクやスレッドが他にない。
たとえこれらの要件がすべて当てはまったとしても、ファイナライザが適当にwaitハンドルのクリーンナップを効率よくやってくれるように見えるかもしれない。したがって、パフォーマンスに問題がない限りは、タスクを解放しないでいられるだろう。
.NET 4.5 Coreの変更
.NET 4.5では、内部のwaitハンドルは明示的にIAsyncResult.AsyncWaitHandleプロパティを読み出したときのみ作られるようになった。それ以外では、Task.WaitAllやTask.WaitAnyを含めて、それが不要なよう再設計されたのだ。そして、async/awaitのための言語サポートが追加されたことにより、多くのシナリオではIAsyncResultももはや不要だ。
.NET 4.5のTaskにおけるもうひとつの変更は、解放した後も利用できることだ。Stephen Toub氏によると、「Taskを解放した後もその公開メンバーはすべて使えるようになっており、解放前と同じように動きます。使えない唯一のメンバーはIAsyncResult.AsyncWaitHandleです。なぜなら、それがTaskインスタンスを解放するときに実際に解放されるものだからです。Taskが解放された後、そのプロパティを使おうとすると、ObjectDisposedExceptionが投げられます。」
したがって.NET 4.5においては、Task.Disposeを呼び出す方が安全ではあるが、それを必要とする理由はほとんどない。
.NET 4.5 Metroの特別ルール
続けてStephen Toub氏は、「Metroスタイルアプリのための.Net」プロファイルを使っているなら、Task.Disposeは存在すらしない、と言う。Taskに関するWinRTドキュメントはまだこの設計変更が反映されていない。
関数からのTask/Task<T>の戻り
「同期メソッドの非同期ラッパーを公開すべきか?」という別の記事で、Stephen氏は関数からのTaskオブジェクトのリターンについて詳しく説明している。この記事を全部読むことをお勧めするが、急いでいる人のために、簡単に紹介する。
公開されるべき非同期メソッドは、同期メソッドよりもスケーラビリティにおいてメリットのあるものだけにすべきだと私は考えています。非同期メソッドは純粋に解放のために公開されるべきではありません。それは、特に同期メソッドを非同期にするようにした機能を使うことで、同期メソッドの消費者が簡単に実現できることなのです。