111

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

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

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

.NET 4では、TPLが推奨される方法です。私がお勧めするのを見たことがある一般的なパターンは、

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

しかしStartNew()メソッドはTask実装するオブジェクトIDisposable。この このパターンを推薦する人たちは見落とされているようです。上のMSDNドキュメントTask.Dispose()方法は言う:

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

タスクが完了するまでdisposeを呼び出すことはできません。そのため、メインスレッドを待機させてdisposeを呼び出しても、バックグラウンドスレッドを最初に実行することの意味がなくなります。クリーンアップに使用される可能性のある完了/終了イベントもありません。

TaskクラスのMSDNページはこれについてコメントしていません、そして、本 "Pro C#2010 ..."は同じパターンを推薦し、タスク処分についてコメントしません。

ファイナライザが最後にそれをキャッチすることを私がちょうどそれを去るかどうか私は知っている、しかしこれは戻って来てそして私がたくさんの火をやっているとき私を噛むつもりである。このようなタスクを忘れると、ファイナライザスレッドは圧倒されますか?

だから私の質問は次のとおりです。

  • 電話しないことは受け入れ可能ですかDispose()Taskこの場合のクラスは?そしてもしそうなら、なぜそしてリスク/結果はありますか?
  • これについて説明している文書はありますか?
  • または適切な処分方法はありますかTask私が逃したオブジェクト?
  • または別の方法で火災を発射します。 TPLでのタスクを忘れる?


3 답변


95

これについての議論がありますMSDNフォーラムで

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

Task.DisposeはTaskのために存在します   イベントハンドルをラップする可能性がある   タスクを待つときに使用されます   イベントが発生した場合は完了   スレッドは実際にブロックしなければなりません。   紡績に反対したり潜在的に   待っているタスクを実行する   あなたがしているのが使っているだけの場合   継続、そのイベントハンドルは   割り当てられない

...

物事の面倒をみるためにはファイナライズに頼るほうがよいでしょう。

アップデート(2012年10月)

Stephen Toubがというタイトルのブログを投稿しました。タスクを破棄する必要がありますか?これはより詳細な情報を提供し、.Net 4.5の改良点を説明しています。

要約すると:あなたは処分する必要はありませんTask99%の割合のオブジェクト。

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

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


  • ありがとう、おもしろい。それはMSDNのドキュメントに反します。これが許容できるコードであるというMSまたは.netチームからの公式の言葉はありますか。その議論の最後に、「実装が将来のバージョンで変更されたらどうなるか」という指摘もあります。 - Simon P Stevens
  • 実際、そのスレッドの回答者がpfxチームのマイクロソフトで実際に機能していることに気付いたので、これは正式な回答であると思います。しかし、すべてのケースでうまくいくとは限らない、その下の方への提案があります。潜在的なリークがある場合、私は安全だとわかっているThreadPool.QueueUserWorkItemに戻るだけのほうがいいですか? - Simon P Stevens
  • はい、あなたが呼ばないかもしれないDisposeがあるのは非常に奇妙です。こちらのサンプルをご覧ください。msdn.microsoft.com/ja-jp/library/dd537610.aspxそしてここmsdn.microsoft.com/ja-jp/library/dd537609.aspx彼らはタスクを破棄していません。ただし、MSDNのコードサンプルでは、非常に悪いテクニックが実証されることがあります。また、Microsoftの質問に答えた。 - Kirill Muzykov
  • @Simon:(1)あなたが引用したMSDNの文書は一般的な助言であり、特定の場合にはより具体的な助言がある(例えば使用する必要はない)。EndInvokeWinFormsで使用する場合BeginInvokeUIスレッドでコードを実行します。 (2)Stephen Toubは、PFXの効果的な使用方法に関する定期的な講演者として非常によく知られています(例:channel9.msdn.comつまり、誰かが良いガイダンスを与えることができれば、彼はそれをしています。彼の2番目の段落に注意してください:ファイナライザに物事を任せることが時々あります。 - Richard
  • これは非常に複雑です... - UserControl

14

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

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

タスクのIsFaultedプロパティに興味があるはずです。それはかなり醜いトピックです、あなたはこれについてもっと読むことができますMSDNライブラリの記事。これを適切に処理したら、タスクを処理するためのコード内にも適切な場所があるはずです。


  • ありがとうハンス。良い比較です。 - Simon P Stevens
  • しかし、タスクが作成されませんThreadほとんどの場合、ThreadPoolを使用します。 - svick

-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());
}


  • もちろん、それは処分することができませんTaskによって返されたインスタンスContinueWithしかし、スティーブントゥーブからの引用が受け入れられた答えであることを参照してください:何もタスクのブロッキング待機を実行しない場合は処分するものがありません。 - Richard
  • リチャードが述べているように、ContinueWith(...)も2番目のTaskオブジェクトを返しますが、それは破棄されません。 - Simon P Stevens
  • このように、ContinueWithコードは、古いタスクを処分するためだけに別のタスクを作成することになるので、実際にはreducedantよりも劣っています。これにより、渡されたアクションデリゲートがタスク自体を操作しようとしていた場合を除き、基本的にこのコードブロックにブロッキング待機を導入することはできません。 - Chris Marisic
  • あなたは2番目のタスクの世話をするためにわずかにトリッキーな方法でラムダがどのように変数を捕らえるかを使うことができるかもしれません。Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); }); - Gideon Engelberth
  • @ GideonEngelberth、それは一見しなければならないでしょう。分散はGCによって処分されることはありませんので、参照がまだ有効/無効であると仮定して、ラムダが処分のために自分自身を呼び出すまで有効である必要があります。たぶんそれの周りに空のtry / catchが必要ですか? - Chris Marisic

リンクされた質問


関連する質問

最近の質問