7

デモンストレーションから始めましょう。

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    Assert.IsNull(h.Target);
}

このコードは期待どおりに機能します。ガベージ コレクションが終了すると、 in の参照hは無効になります。さて、ここにひねりがあります:

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    try { }      // I just add an empty
    finally { }  // try/finally block
    Assert.IsNull(h.Target); // FAIL!
}

行の後に空の try/finally ブロックをテストに追加するとGC.Collect()、弱い参照オブジェクトが収集されません! 行の前に空の try/finally ブロックが追加されてGC.Collect()いる場合でも、テストはパスします。

何を与える?try/finally ブロックがオブジェクトの寿命にどのように影響するかを正確に説明できる人はいますか?

注: すべてのテストはデバッグで行われます。リリースでは、両方のテストに合格します。

注 2: アプリを再現するには、.NET 4 または .NET 4.5 ランタイムのいずれかをターゲットにする必要があり、32 ビットとして実行する必要があります (x86 をターゲットにするか、[32 ビット優先] オプションがオンになっている任意の CPU)。

4

2 に答える 2

3

デバッガーが接続されている場合、ジッターはローカル変数の有効期間を変更します。この回答で詳しく説明されています。簡単に言うと、デバッガーを使用しない場合、有効期間はコード内で変数が最後に使用された時点で終了します。デバッガーを使用すると、ウォッチ デバッガー式が機能するようにメソッドの最後まで延長されます。

式がコード内の変数に格納されていないように見えnew object()ますが、ジッター コード ジェネレーターがそれを処理した後もまだ格納されています。オブジェクト参照はスタック フレームの [ebp-44h] に格納され、ローカル変数の使用方法と区別できません。これを確認する唯一の方法は、生成されたマシン コードを確認することです。Debug + Windows + Disassembly を使用します。それ以外の場合、これは完全に正常です。この種の冗長なメモリ ストアは、ジッター オプティマイザーによって排除されますが、デバッグ ビルドでは有効になりません。

一時的なものですが、この変数は参照を格納するものとして GC に報告する必要があります。オブジェクト コンストラクター呼び出しと WeakReference コンストラクター呼び出しの間で GC が発生したときに、オブジェクトが収集されないようにするために必要です。プログラム内の別のスレッドがコレクションをトリガーする場合に可能です。

try/finally ブロックがなくても、ジッターはスタック フレーム スロットが一時的なものを格納していることを発見でき、実際にはその有効期間を延長する必要はありません。そのため、GC.Collect() 呼び出しの前に一時オブジェクトの有効期間の報告を停止し、オブジェクトが収集されます。

しかし、try/finally ブロックを使用すると、try ブロックまたは finally ブロックでスタック フレーム スロットが使用される可能性があるかどうかを把握しようとする試みが、ジッターによって断念されます。そして、通常のローカル変数で起こるように、その寿命をメソッドの最後まで延長するだけで問題を解決します。

これはごく普通のことであり、最適化されていないコードでローカル変数参照がどのように処理されるかについて、合理的な仮定を立てることはできません。これは、ユニット テスターで実行される [TestMethod] を実際に使用するすべての人に対する強い警告でもあります。コードのデバッグ ビルドは決してテストせず、リリース ビルドのみをテストしてください。ユーザーのマシンでの動作と同じようには動作しません。

于 2013-09-05T10:45:32.977 に答える
0

デバッグを容易にするために、デバッグ モードでは、ローカルで宣言されたオブジェクトは破棄されません。私はあなたの問題を再現できませんでしたが、次のコードで:

var x = new object();
var h = new WeakReference(x);
GC.Collect();
try { }      // I just add an empty
finally { }  // try/finally block
Console.WriteLine(h.Target != null);
Console.ReadKey();

問題を再現できました。GC.Collect()を「収集」できた場合new object()、 の後にブレークポイントをConsole.ReadKey()配置すると、既に破棄されたオブジェクト ( ) を表示できますx

誰かがここで似たようなことを尋ねました: https://stackoverflow.com/a/755688/613130

コメントは興味深いです:

リリース モードでの最適化により、参照スコープは最後に使用されるまでのみ有効であり、それが定義されているコード ブロック全体では有効ではありません。

明らかにデバッグモードでは逆です。参照スコープは、それが定義されているスコープ全体です。

于 2013-09-05T09:34:40.593 に答える