4

私の知る限り、IDisposableであるメンバーmを持つクラスAがある場合、AはIDisposableを実装し、その中でm.Dispose()を呼び出す必要があるというのが受け入れられたルールです。

これが事実であるという満足のいく理由を見つけることができません。

管理されていないリソースがある場合は、IDisposableとともにファイナライザーを提供する必要があるというルールを理解しています。これにより、ユーザーが明示的にDisposeを呼び出さなくても、ファイナライザーはGC中にクリーンアップされます。

ただし、そのルールが設定されていれば、この質問に関するルールを設定する必要はないようです。例えば...

クラスがある場合:

class MyImage{
  private Image _img;
  ... }

コンベンションは私が持っているべきであると述べていMyImage : IDisposableます。しかし、Imageが規則に従い、ファイナライザーを実装していて、リソースのタイムリーなリリースを気にしない場合、ポイントは何ですか?

アップデート

私がここで得ようとしていたことについての良い議論を見つけました。

4

7 に答える 7

22

しかし、Imageが規則に従い、ファイナライザーを実装していて、リソースのタイムリーなリリースを気にしない場合、ポイントは何ですか?

あなたは完全に処分のポイントを逃しました。それはあなたの便利さではありません。それは、これらの管理されていないリソースを使用する可能性のある他のコンポーネントの利便性に関するものです。システム内の他のコードがリソースのタイムリーなリリースを気にしないことを保証できず、ユーザーがリソースのタイムリーなリリースを気にしない場合を除いて、できるだけ早くリソースをリリースする必要があります。それは丁寧なことです。

古典的な囚人のジレンマでは、協力者の世界で唯一の亡命者が大きな利益を得る。しかし、あなたの場合、孤独な脱北者であることは、低品質でベストプラクティスを無視するコードを書くことによって、個人的に数分を節約するというほんのわずかな利益しか生み出しません。苦しむのはユーザーとユーザーが使用するすべてのプログラムであり、実質的に何も得られません。あなたのコードは、他のプログラムがファイルのロックを解除し、ミューテックスなどを解放するという事実を利用しています。良い市民になり、彼らのために同じことをしてください。それは難しいことではなく、ソフトウェアエコシステム全体をより良くします。

更新:これは、私のチームが現在対処している実際の状況の例です。

テストユーティリティがあります。管理されていないリソースの束が積極的に廃棄されないという点で、「ハンドルリーク」があります。「タスク」ごとにおそらく半ダースのハンドルがリークしています。無効になっているテストなどを検出したときに、「実行するタスク」のリストを保持します。このリストには1万から2万のタスクがあるため、すぐに非常に多くの未処理のハンドル(デッドになってオペレーティングシステムにリリースされるはずのハンドル)ができてしまい、すぐにシステム内のコードがなくなってしまいますテストに関連して実行できます。テストコードは気にしません。それはうまく機能します。ただし、最終的には、テスト対象のコードでメッセージボックスやその他のUIを作成できず、システム全体がハングまたはクラッシュします。

ガベージコレクターは、これらのハンドルをより早く解放するために、ファイナライザーをより積極的に実行する必要があることを知る理由はありません。なぜそれが必要ですか?その仕事はメモリを管理することです。あなたの仕事はハンドルを管理することなので、あなたはその仕事をしなければなりません。

于 2011-05-23T19:35:05.580 に答える
6

しかし、Imageが規則に従い、ファイナライザーを実装していて、リソースのタイムリーなリリースを気にしない場合、ポイントは何ですか?

タイムリーなリリースを気にしないのであれば、それはありません。使い捨てオブジェクトが正しく書かれていることを確認できます(実際、MSコードを使用していても、そのような仮定は決してしません。いつ何かがわからないのです。誤ってすり抜けた)。重要なのは、いつ問題が発生するかわからないため、注意する必要があるということです。開いているデータベース接続について考えてみてください。ぶら下げたままにしておくと、プールで交換されないことを意味します。1つに複数のリクエストが入ってくると、不足する可能性があります。

あなたが気にしないならあなたがそれをしなければならないということは何も言いません。このように考えると、アンマネージプログラムで変数を解放するようなものです。必ずしもそうする必要はありませんが、強くお勧めします。他の理由がなければ、プログラムから継承している人は、なぜそれが世話をされなかったのか疑問に思う必要はなく、それからそれを片付けようとします。

于 2011-05-23T18:52:02.240 に答える
2

まず、ファイナライザスレッドによってオブジェクトがいつクリーンアップされるかは保証されません。クラスにSQL接続への参照がある場合を考えてみてください。これがすぐに破棄されることを確認しない限り、接続は不明な期間開いたままになり、再利用することはできません。

次に、ファイナライズは安価なプロセスではありません。オブジェクトが適切に破棄されている場合は、ファイナライズが発生しないようにGC.SuppressFinalize(this)を呼び出していることを確認する必要があります。

「安くはない」という側面を拡張すると、ファイナライザースレッドは優先度の高いスレッドになります。あなたがそれをするのにあまりにも多くを与えるならば、それはあなたのメインアプリケーションからリソースを奪うでしょう。

編集:わかりました、これはファイナライズについてのクリス・ブルミーによるブログ記事です。なぜそれが高価なのかを含みます。(私はこれについての負荷をどこかで読むことを知っていました)

于 2011-05-23T18:57:55.360 に答える
1

リソースのタイムリーなリリースを気にしないのであれば、確かに意味がありません。コードがあなたの消費のためだけのものであり、十分な空きメモリ/リソースがあることを確信できる場合は、GCが選択したときにそれをフーバーさせてみませんか。OTOH、他の誰かがあなたのコードを使用していて、(例えば)の多くのインスタンスを作成している場合MyImage、それがうまく処理されない限り、メモリ/リソースの使用を制御することはかなり難しいでしょう。

于 2011-05-23T18:53:56.280 に答える
1

多くのクラスでは、正確性を確保するためにDisposeを呼び出す必要があります。たとえば、一部のC#コードが「finally」ブロックを持つイテレータを使用する場合、そのイテレータを使用して列挙子が作成され、破棄されないと、そのブロック内のコードは実行されません。ファイナライザーなしでオブジェクトをクリーンアップすることが実際的でない場合もありますが、ほとんどの場合、正しい操作またはメモリリークを回避するためにファイナライザーに依存するコードは不正なコードです。

コードがIDisposableオブジェクトの所有権を取得する場合、オブジェクトのクリースが封印されるか、コードがコンストラクターを呼び出してオブジェクトを作成しない限り(ファクトリメソッドではなく)、オブジェクトの実際のタイプを知る方法はありません。 、そしてそれが安全に放棄できるかどうか。Microsoftは当初、あらゆる種類のオブジェクトを安全に放棄することを意図していたかもしれませんが、それは非現実的であり、あらゆる種類のオブジェクトを安全に放棄する必要があるという信念は役に立ちません。オブジェクトがイベントをサブスクライブする場合、安全な放棄を可能にするには、すべてのイベントに弱い間接参照のレベルを追加するか、他のすべてのアクセスに(弱いではない)間接参照のレベルを追加する必要があります。多くの場合、放棄を可能にするためにかなりのオーバーヘッドと複雑さを追加するよりも、呼び出し元がオブジェクトを正しく破棄することを要求する方が適切です。

また、オブジェクトが放棄に対応しようとしても、非常に高額になる可能性があることにも注意してください。Microsoft.VisualBasic.Collection(またはそれが呼ばれるもの)を作成し、いくつかのオブジェクトを追加し、100万個の列挙子を作成して破棄します。問題ありません-非常に迅速に実行されます。次に、100万の列挙子を作成して破棄します。数千の列挙子ごとにGCを強制しない限り、主要なスヌーズフェスト。Collectionオブジェクトは、放棄できるように作成されていますが、大きなコストがかからないという意味ではありません。

于 2011-05-24T15:30:04.190 に答える
1

使用しているオブジェクトがIDisposableを実装している場合、それは、使い終わったときに何か重要なことをしなければならないことを示しています。その重要なことは、管理されていないリソースを解放すること、またはイベントからフックを解除して、イベントが終了したと思った後にイベントを処理しないようにすることなどです。Disposeを呼び出さないことで、よりよく知っていると言っていることになります。そのオブジェクトが元の作成者よりもどのように動作するかについて。いくつかの小さなエッジケースでは、IDisposableクラスを自分で作成した場合、またはDisposeの呼び出しに関連するバグやパフォーマンスの問題を知っている場合、これは実際に当てはまる可能性があります。一般に、クラスが終了したときにそれを破棄するように要求するクラスを無視することは、ほとんどありません。

ファイナライザーについて話すと、指摘されているように、コストがかかります。これは、オブジェクトを破棄することで回避できます(SuppressFinalizeを使用している場合)。ファイナライザー自体を実行するコストだけでなく、GCがオブジェクトを収集する前にファイナライザーが完了するまで待機する必要があるコストだけでもありません。ファイナライザーを持つオブジェクトは、未使用でファイナライズが必要であると識別されたコレクションを存続させます。したがって、昇格されます(まだ第2世代に含まれていない場合)。これにはいくつかのノックオン効果があります。

  • 次の上位世代は収集される頻度が低くなるため、ファイナライザーが実行された後、GCがその世代に到達してオブジェクトを一掃するまで、長い間待機する可能性があります。そのため、メモリを解放するのにはるかに長い時間がかかる可能性があります。
  • これにより、オブジェクトがプロモートされるコレクションに不要なプレッシャーが追加されます。gen0からgen1に昇格した場合、gen1は必要以上に早くいっぱいになります。
  • これは、より高い世代でより頻繁なガベージコレクションにつながる可能性があり、これは別のパフォーマンスへの影響です。
  • GCが上位世代に到達するまでにオブジェクトのファイナライザーが完了していない場合は、オブジェクトを再度昇格させることができます。したがって、悪いケースでは、正当な理由なしにオブジェクトを第0世代から第2世代に昇格させることができます。

明らかに、これを1つのオブジェクトに対してのみ実行している場合、目立ったコストがかかる可能性はほとんどありません。使用しているオブジェクトに対してDisposeを呼び出すのが面倒であるために一般診療として行っている場合は、上記のすべての問題が発生する可能性があります。

処分は玄関の鍵のようなものです。それはおそらく理由があり、建物を離れる場合は、おそらくドアをロックする必要があります。それをロックするのが良い考えでなければ、ロックはありません。

于 2011-05-26T20:25:48.033 に答える
0

この特定のケースで気にしない場合でも、場合によっては気にするので、標準に従う必要があります。時々無視する基準を持つよりも、基準を設定し、常に特定のガイドラインに基づいてそれに従う方がはるかに簡単です。これは、チームが成長し、製品が古くなるにつれて特に当てはまります。

于 2011-05-23T19:01:58.290 に答える