1

c#/xna のレンダー ターゲットは自動的に破棄されず、それらを削除するには .dispose() メンバーを呼び出す必要があることがわかりました。

ガベージ コレクションは、すべての参照がなくなった後に自動的に物を取り除くものだと思っていましたが、何が得られますか?

自動的に破棄されないものは他にありますか?

4

4 に答える 4

10

ガベージ コレクションは、すべての参照がなくなった後に自動的に物を取り除くものだと思っていましたが、何が得られますか?

これは 2 つの点で正しくありません。

ガベージ コレクションは、安全に収集でき、必要なマネージド メモリを収集します。それでおしまい。

ガベージ コレクションは、無限ヒープ メモリをシミュレートする方法と考えてください。無限のメモリがあるふりをすることができるので、使い終わったメモリを解放するために何かを呼び出す必要はありません。

GC が無限ヒープをシミュレートする最も簡単な方法は、何もしないことです。これは、たとえば、プロセスで 4GiB のメモリが利用可能で、50MiB を使用する場合に機能します。収集は決して起こりません。これは、コレクションが発生しないほどアプリが小さい場合に実際に発生します。(コレクションなしで megs を使用できるようにするほど怠惰ではありませんが、OS にアプリ用のメモリを追加するように要求する前に収集を試みますが、「なぜ GC を使用しなかったのか」と疑問に思っている場合に役立ちます。 .." 何もしないことが有効な GC アプローチである場合もありますが、その可能性を念頭に置いておけば、他の多くの質問はなくなります)。

別のアプローチは、クリーンアップできる瞬間にすべてを熱心にクリーンアップすることです。これは、参照カウント ガベージ コレクタで発生します。これは.NETとは何の関係もありませんが、あなたの質問の「すべての参照がなくなった後」と密接に一致するため、言及する価値があります。

もう 1 つは、メモリが必要であり、既に解放されているメモリのストアで使用できない場合、GC はすべてのスレッドを停止し、ルート (静的変数と各スレッドのスタック内のローカル参照) を識別し、ルートによって参照されるすべてのものを識別します。そして、それらによって参照されるすべてのものなど、アプリケーションによってまだ実行可能に使用される可能性があるすべてが見つかるまで続き、それ以外のすべてによって使用されるメモリは空きと見なされます。次に、メモリが解放されていないすべてのオブジェクトを圧縮します。これにより、より多くの空きメモリを提供する際の断片化が回避され、多くの場合、オブジェクトがメモリ内でより近くに保持されます (パフォーマンス上のわずかな利点があります)。

削除しなかったオブジェクトも「昇格」する場合、最初に削除するのが正しくなかった可能性があるため、次回はそうではないため、このプロセスを生き残ったオブジェクトをあまり調べません。頻繁。

この時点で注意すべき点は次の 2 つです。

  1. GC が特定のオブジェクトのメモリをいつ解放するかを予測することはできません。
  2. GC が行う唯一のことは、マネージド メモリを解放することです。それ以外には何もしません (ファイナライザーには役立ちますが、これについては後で説明します)。

もちろん、マネージ メモリを取得してから解放することは、最初に何かを実行してから元に戻したい場合の 1 つのケースにすぎません。その他の例は次のとおりです。

  1. ファイルハンドルの取得と解放。
  2. Windows ハンドルを取得してから解放します。
  3. GDI ハンドルを取得して解放します。
  4. ネットワーク接続を開き、閉じます。
  5. ネットワーク接続を介してプロトコル定義のハンドシェイクを送信し、それを閉じる前にプロトコル定義のサインオフを送信します。
  6. アンマネージ メモリを取得して解放します。
  7. プールからオブジェクト (その作成への割り当てを超えたオーバーヘッドがあるか、使用を通じて「学習」するオブジェクト) を取得し、それをプールに返す。

これまで説明してきた GC は、これらのいずれにも役立ちません。

それでも、それらにはすべて2つの共通点があります。開始操作と終了操作があります。

開始操作は、オブジェクトの作成または何らかのメソッド呼び出しに適切にマップされます。

Close()終了操作は、End()Free()Release()メソッド呼び出しに一致する可能性がありますが、定義IDisposable.Dispose()では、それらすべてに共通のインターフェイスを与えることができます。using言語は、 †を介していくつかのヘルプを追加することもできます。

(クラスは aClose()と a の両方Dispose()を持つことができます。この場合、後で再度開くか、閉じた状態で使用するものを閉じるオプションと、終了後にクリーンアップを保証する方法の両方があります。物体)。

このように、マネージ メモリを除いIDisposable.Dispose()て、クリーンアップが必要なすべてのものをクリーンアップするために存在します。

さて、この場合、実装する必要がある 3 種類のクラスがありますIDisposable

  1. ハンドルのような管理されていないリソースを保持するもの。
  2. ある種のプーリングまたは独自の考案 (または他の誰かの考案、ただしすべて .NET 自体内) の他の前後のシナリオで使用しているもの。
  3. 順番に実装するフィールドを持っているIDisposableため、このクラスのオブジェクトを破棄するとDispose()、それらのフィールドが呼び出されます。

Dispose()GC がそのようなオブジェクトのメモリを解放し、呼び出されていない場合に何が起こるかを考えてみましょう。

3 番目のケースでは、オブジェクトが破棄されていなくても問題ありません。本当に重要なのは、フィールドが破棄されていないことです (または、おそらくそれは問題ではありませんが、その下にあるフィールドが重要です)。

2 番目のケースでは、その重要性は、プーリングがどれほど重要であったかによって異なります。それはおそらく最適ではありませんが、世界の終わりではありません.

最初のケースでは、これは惨事です。アプリケーションが終了するまでリリースできない未リリースのリソースがあり、おそらくその後もリリースできません (リソースの性質によって異なります)。

このため、オブジェクトはファイナライザーを持つことができます。

GC がオブジェクトのメモリを解放しようとしているとき、ファイナライザーがあり、そのファイナライザーが抑制されていない場合 (Dispose()通常、オブジェクトが適切にクリーンアップされ、それ以上の作業が必要ないことを示すためにこれを行います) )、オブジェクトからメモリを解放する代わりに、ファイナライズ キューに入れます。もちろん、これは、そのオブジェクトがメモリを収集していないだけでなく、そのフィールドを介して到達可能なオブジェクトも収集していないことを意味します。

ファイナライザー スレッドは、このキューを介して処理し、それぞれのファイナライザー メソッドを呼び出します。

これには 2 つの悪い点があります。

  1. それがいつ起こるかはわかりません。おそらく、リソースが不足するか、ファイルを開く前に書き込み用に開くことができなくなるでしょう。
  2. これは、メモリが解放されているはずのオブジェクトが代わりにプロモートされ、本来あるべきよりも 1 サイクルだけ長く存続するだけでなく、何サイクルも長く存続することを意味します。

編集: 3 番目の種類のクラスにはファイナライザーがなく、おそらく 2 番目の種類ではないことに注意してください。この場合、ファイナライザーは必要ありません。なぜなら、フィールドとして持っていた本当に重要なオブジェクトには、重要な作業を行うファイナライザーが呼び出されるからです。また、ファイナライザー内からファイナライズ可能なフィールドを処理しようとすると、非常に簡単に致命的なバグが発生します。「所有」し、クリーンアップを担当する 1 つ以上の使い捨てフィールドをラップする使い捨てクラスを作成する場合は、 を実装IDisposableしますが、ファイナライザーは追加しないでください。

全体として、呼び出されるファイナライザーは次の 2 つのいずれかを意味します。

  1. アプリケーションがシャットダウンされ、すべてのファイナライザーが実行されています (すべて順調です)。
  2. 誰かがめちゃくちゃにして、あるべきときに何かをクリーンアップしませんでした。

そのため、ファイナライザーを介して GC とマネージ メモリ以外のリソースとの間で対話が行われますが、これは最後の手段の対話であり、決して信頼できるものではありません。ファイナライザーを GC にクリーンアップを行わせる方法と考えるべきではありませんが、GC がクリーンアップを不可能にしないための方法として (そしてクリーンアップを行う方法として) と考えるべきです。 -アプリケーションのシャットダウン時にアップ)。

*もちろん、無限の資源 (魚、水牛、海洋の廃棄物処理能力) があると思っていたのに、そうでないことが判明した場合、事態は混乱する可能性があるため、そのルールをすべてに適用しないでください。生活。

†<code>using を使用すると、呼び出しがDispose()簡単になります。

using(someDisposableObject)
{
  //Do Stuff
}

以下と同等です。

try
{
  //Do Stuff
}
finally
{
  if(someDisposableObject != null)
    ((IDisposable)someDisposableObject).Dispose();
}

と:

using(var someDisposableObject = someMethodCallOrCallToNew())
{
  //Do Stuff
}

以下と同等です。

var someDisposableObject = someMethodCallOrCallToNew();
try
{
  //Do Stuff
}
finally
{
  if(someDisposableObject != null)
    ((IDisposable)someDisposableObject).Dispose();
}

最適化として、 someDisposableObject を null にすることは不可能であるとコンパイラが判断できる場合、null チェックを削除できます。

于 2012-08-13T15:09:26.093 に答える
1

存在する理由IDisposableは、ガベージコレクションがオブジェクトメモリを[効果的に]管理できない例外的なケースをサポートするためです。それが存在する主な理由は、管理されていないリソースを解放するためです。

これの一般的なケースの1つは、管理されていないメモリとの相互作用です。オブジェクトがガベージコレクターのスコープ外にメモリを割り当てることを含む何かをしているとき、ガベージコレクターはそれをクリーンアップする責任がありません。プログラマーが処理する必要があります。これらのケースでは、大量のメモリが割り当てられる可能性も高くなるため、メモリをより迅速にクリーンアップすることが非常に重要になります。

また、オブジェクトに、実際にメモリを解放する以外に実行する必要があるある種の「クリーンアップ」がある場合にも使用されます。例としては、IOを処理するときのファイルハンドラー、または閉じる必要のあるデータベースへの接続があります。ガベージコレクターにオブジェクトのメモリを解放させると、このタイプのクリーンアップは実行されません。

プログラマーがIDisposeableインターフェースを使用してステートメントの構文を「ハイジャック」し、using実際にはそのdisposeメソッドにアンマネージリソースが破棄されていない場合もあります。

于 2012-08-13T14:34:42.917 に答える
1

Dispose自動ガベージコレクションを使用する場合でも、インスタンスを再度使用しないことが確実な場合は、呼び出すことをお勧めします。

または、usingステートメントを使用します。

そうすれば、ガベージコレクターが実行するまで待つのではなく、オブジェクトを「破棄」するタイミングを選択できます。

于 2012-08-13T14:23:36.127 に答える
0

私の意見では、オブジェクトに Dispose メソッドがある場合は、おそらく破棄する必要があります。私は通常、「using」ステートメントを使用してブロックに入れようとします。これにより、ブロックの最後で Dispose メソッドが自動的に呼び出されます。

于 2012-08-13T14:25:39.357 に答える