2

.NET のマネージド コードとアンマネージド コードの間の時期尚早なガベージ コレクションを把握するのに本当に苦労しています。

私たちが持っていたバグは、ここで説明されているものと似ていました: http://www.codeproject.com/Tips/246372/Premature-NET-garbage-collection-or-Dude-wheres-my

基本的に、アンマネージ コードを呼び出すオブジェクトのメソッドを呼び出しています。それを支えるマネージド オブジェクトが GC され、そのファイナライザーが呼び出されます。これが起こらないようにするには、GC.KeepAlive が必要です。

(リンクされた記事から取得したコード):

Foo a = new Foo();
while (true)
{
    FooBar b = new FooBar();
    b.WorkWith(a);
    GC.KeepAlive(b);
}

今、私は GC.KeepAlive が必要であることを理解していますが、GC が KeepAlive なしで b を破棄できるという結論に達する方法を理解していません。ランタイムは、b がネイティブ コードに組み込まれたメソッドであっても、メソッドの実行中にあることを認識しませんか (具体的には、'b' がメソッド呼び出しで this 参照として使用されています)。

WorkWith(..) へのエントリで 'b' がコレクションの対象となるのはなぜですか? WorkWith メソッドの終了時に 'b' が対象になる可能性があるとガベージ コレクターが想定しないのはなぜですか?

私は何が欠けていますか?これは実際にどのように機能しますか?

更新皆さん、ご回答ありがとうございます。これでやっと理解できた気がします。私たちの特定のAPIのための良い解決策をまだ考え出そうとしていますが、それは別の質問に任せると思います:)

4

2 に答える 2

0

this仮想メソッド ディスパッチでは少し複雑になりますが、インスタンス メソッドは、最初のパラメーターとして渡される静的メソッドとほぼ同じように動作します。したがって、 class 内Fooで、フィールドint Barにはメソッドがあります

void SetBar(int newBar) { Bar = newBar; }

内部的には次と同等です。

static void SetBar(Foo This, int newBar) { This.Bar = newBar; }

アンマネージ ビットマップへのハンドルを保持し、次のようなメソッドをFoo持つフィールドが呼び出されたとします。MyBitmapIntPtr

// Should call GC.SuppressFinalize, but doesn't.
static Byte[] ExportBitmapDataAndDispose(Foo This)
{
  IntPtr myBits = This.MyBitMap;
  if (myBits == 0) throw new ObjectDisposedException(...);
  int destSize = ExternalBitmapHandler.GetSize(myBits);
  var result = new Byte[destSize];
  ExternalBitmapHandler.CopyBits(myBits, ref result[0], destSize);
  ExternalBitmapHandler.ReleaseBits(myBits);
  myBits = 0;
}

コンパイラはThis、フィールドMyBitMapが読み取られた後、それが使用されていないことを認識します。コードは で識別される外部リソースを必要としますmyBitsが、ガベージ コレクターはそのようなことを何も知りません。その観点からは、によって参照されるオブジェクトにThis他のライブ参照がない場合、コードは、それらのオブジェクトがその時点で単に存在しなくなったのか、それともそれより長く保持されていたのかを気にする必要はありません。実際、実行中のコードはそのようなオブジェクトがいつ存在しなくなったかを実際に気にしないという点で、その仮定は正しいです。残念ながら、ファイナライザーが存在する場合、オブジェクトは単に存在しなくなるわけではありません。代わりに、ファイナライゼーション キューが への唯一のライブ参照を保持していることにガベージ コレクターが気付いた場合、ガベージ コレクターThisは実行This.Finalize()中の可能性があります。ExternalBitmapHandlerによって識別されたビットマップmyBitsはもはや必要ありません。

問題は実際には のガベージ コレクションThisはなく、 のファイナライズにあることに注意してくださいThis。ファイナライズはガベージ コレクションではなく、代わりに、登録されたファイナライザーが対象でなかった場合に即時消滅の対象となるオブジェクトを GC が検出したときにトリガーされる操作です。別の見方をすると、 でExternalBitmapHandler識別されるビットマップThis.MyBitMapが不要になったときに に通知する必要がありますが、ガベージ コレクターが伝えることができる唯一のことは、Thisが不要になったときです。コードが に読み込まThis.MyBitMapれるmyBitsと、それはもう必要Thisありません。This「myBits. コードの代替バージョンは次のようになります。

static Byte[] ExportBitmapDataAndDispose(Foo This)
{
  IntPtr myBits = System.Threading.Interlocked.Exchange(ref This.MyBitMap, 0);
  GC.SuppressFinalize(This);
  if (myBits == 0) throw new ObjectDisposedException(...);

  int destSize = ExternalBitmapHandler.GetSize(myBits);
  try
  {
    var result = new Byte[destSize]; // Could throw OutOfMemoryException
    ExternalBitmapHandler.CopyBits(myBits, ref result[0], destSize);
  }
  finally
  {
    ExternalBitmapHandler.ReleaseBits(myBits);
  }
}

この場合、 がなくKeepAliveSuppressFinalizeすべてが完了するまでファイナライズの可能性を延期するコードが最後に配置されていないことに注意してください。が発生するInterlocked.Exchangeと、システムが を破棄しても何の問題もありThisません。システムは、実行するのに十分な時間それを保持する必要がありますSuppressFinalizeが、その後は消えて誰も気付かない可能性があります.

于 2013-09-13T17:44:17.673 に答える