TPL TaskオブジェクトでDispose()を呼び出さないことは許容できると考えられますか?


123

バックグラウンドスレッドで実行するタスクをトリガーしたい。タスクの完了を待ちたくありません。

.net 3.5では、私はこれをしたでしょう:

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });

.net 4では、TPLが推奨される方法です。私が推奨する共通のパターンは次のとおりです。

Task.Factory.StartNew(() => { DoSomething(); });

ただし、StartNew()メソッドはTaskを実装するオブジェクトを返しますIDisposable。これは、このパターンを推奨する人々から見落とされているようです。Task.Dispose()メソッドに関するMSDNのドキュメントは言う:

「タスクへの最後の参照を解放する前に、必ずDisposeを呼び出してください。」

タスクが完了するまでタスクのdisposeを呼び出すことはできないため、メインスレッドを待機させてdisposeを呼び出すと、そもそもバックグラウンドスレッドで実行するポイントが無効になります。また、クリーンアップに使用できる完了/終了したイベントはないようです。

TaskクラスのMSDNページはこれについてコメントしておらず、 "Pro C#2010 ..."という本でも同じパターンを推奨しており、タスクの破棄についてはコメントしていません。

私がそのままにしておくとファイナライザーが最後にそれをキャッチしますが、これが戻ってきて、このような多くの火事をしていてファイナライザースレッドが圧倒されると、私に噛み付くのでしょうか?

だから私の質問は:

  • この場合Dispose()Taskクラスを呼び出さなくても問題ありませんか?もしそうなら、なぜそしてリスク/結果はありますか?
  • これについて説明しているドキュメントはありますか?
  • それともTask私が見逃したオブジェクトを処分する適切な方法はありますか?
  • または、TPLを使用してタスクを実行して忘れる別の方法はありますか?

回答:


108

これについては、MSDNフォーラムで議論されています

Microsoft pfxチームのメンバーであるStephen Toubは、次のように述べています。

Task.Disposeは、タスクが完了するのを待機するときに使用されるイベントハンドルをラップする可能性があるため、待機中のスレッドが実際にブロックする必要がある場合(待機しているタスクのスピンまたは潜在的な実行とは対照的に)に存在します。あなたがしているすべてが継続を使用している場合、そのイベントハンドルは決して割り当てられません
...
事の面倒を見るにはファイナライズに頼る方が良いでしょう。

更新(2012年10月)
Stephen Toubが、「タスクを破棄する必要がありますか?」というブログを投稿しましたこれにより、さらに詳細が提供され、.Net 4.5の改善点が説明されます。

要約すると、Task99%の確率でオブジェクトを廃棄する必要はありません。

オブジェクトを破棄する主な理由は2つあります。アンマネージリソースをタイムリーかつ確定的な方法で解放することと、オブジェクトのファイナライザーを実行するコストを回避することです。これらはどちらもTaskほとんどの場合当てはまりません。

  1. ネット4.5、唯一の時間としてTask割り当て内部待機ハンドル(唯一の非管理リソースTaskオブジェクト)が明示的に使用する場合であるIAsyncResult.AsyncWaitHandleのをTask、そして
  2. Taskオブジェクト自体は、ファイナライザを持っていません。ハンドル自体はファイナライザーを備えたオブジェクトにラップされているため、割り当てられない限り、実行するファイナライザーはありません。

3
ありがとう、面白い。ただし、MSDNのドキュメントに反します。これが許容可能なコードであるという、MSまたは.netチームからの公式の言葉はありますか?その議論の最後に、「将来のバージョンで実装が変更された場合はどうなるのか」という指摘もある
Simon P Stevens

実際、そのスレッドの回答者が実際にMicrosoftで働いているように見えます。これは、PFXチームのように見えるので、これは一種の公式の回答だと思います。しかし、すべてのケースで機能するとは限らないという提案があります。潜在的なリークがある場合は、安全であることがわかっているThreadPool.QueueUserWorkItemに戻すだけの方がいいですか。
Simon P Stevens

はい、呼び出すことができないDisposeがあるのは非常に奇妙です。ここmsdn.microsoft.com/en-us/library/dd537610.aspxとここmsdn.microsoft.com/en-us/library/dd537609.aspxのサンプルを見ると、それらはタスクを破棄していません。ただし、MSDNのコードサンプルは、非常に悪い手法を示す場合があります。また、質問に答えた人はマイクロソフトのために働きます。
キリル・ムジコフ

2
@Simon:(1)引用するMSDNドキュメントは一般的なアドバイスであり、特定のケースにはより具体的なアドバイスがあります(たとえば、UIスレッドでコードを実行するためEndInvokeにWinFormsで使用する必要はありませんBeginInvoke)。(2)Stephen Toubは、PFXの効果的な使用(例:channel9.msdn.com)に関する通常の講演者として非常によく知られています。彼の2番目の段落に注意してください。場合によっては、ファイナライザに任せた方が良い場合があります。
Richard

12

これはThreadクラスと同じ種類の問題です。5つのオペレーティングシステムハンドルを消費しますが、IDisposableを実装していません。元の設計者の良い決定です。もちろん、Dispose()メソッドを呼び出す合理的な方法はいくつかあります。最初にJoin()を呼び出す必要があります。

Taskクラスは、これに1つのハンドル、内部手動リセットイベントを追加します。どれが最も安価なオペレーティングシステムリソースです。もちろん、そのDispose()メソッドは、その1つのイベントハンドルのみを解放でき、Threadが消費する5つのハンドルは解放できません。 うん、気にしないで

タスクのIsFaultedプロパティに関心を持つべきであることに注意してください。これはかなり醜いトピックです。詳細については、このMSDNライブラリの記事を参照してください。これを適切に処理したら、タスクを破棄するためのコード内の良い場所も必要です。


6
ただし、Threadほとんどの場合、タスクはを作成せず、ThreadPoolを使用します。
11

-1

この投稿に示されている手法について誰かが検討するのを見てみたい:C#でのタイプセーフなファイアアンドフォーゲット非同期デリゲートの呼び出し

単純な拡張メソッドは、タスクとやり取りするすべての些細なケースを処理し、それに対してdisposeを呼び出すことができるように見えます。

public static void FireAndForget<T>(this Action<T> act,T arg1)
{
    var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                     TaskCreationOptions.LongRunning);
    tsk.ContinueWith(cnt => cnt.Dispose());
}

3
もちろん、それTaskはによって返されたインスタンスの破棄に失敗しContinueWithますが、Stephen Toubからの引用が受け入れられた答えであることを確認してください:タスクでブロッキング待機を実行するものがなければ、破棄するものはありません。
Richard

1
Richardが言及しているように、ContinueWith(...)は、破棄されない2番目のTaskオブジェクトも返します。
Simon P Stevens

1
つまり、これは現状のままです。ContinueWithコードは冗長よりも実際には悪いです。これは、古いタスクを破棄するためだけに別のタスクが作成されるためです。これでは、渡されたアクションデリゲートがタスク自体を操作しようとしていた場合を除いて、基本的にこのコードブロックにブロッキング待機を導入することはできませんか?
Chris Marisic

1
ラムダが変数をキャプチャする方法を少しトリッキーな方法で使用して、2番目のタスクを処理できる場合があります。Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
Gideon Engelberth

@GideonEngelberth機能するようです。disperは決してGCによって破棄されるべきではないため、参照が/ shrugでまだ有効であると想定して、ラムダが破棄するために自身を呼び出すまで、ディスパーは有効なままである必要があります。多分それの周りに空のtry / catchが必要ですか?
Chris Marisic、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.