5
[TestFixture]
public class Tests
{
    private class Relay
    {
        public Action Do { get; set; }
    }

    [Test]
    public void OptimizerStrangeness()
    {
        var relay = new Relay();
        var indicator = 0;
        relay.Do = () => indicator++;
        var weak = new WeakReference(relay);

        GC.Collect();

        var relayNew = weak.Target as Relay;
        if (relayNew == null) Assert.Fail();
        relayNew.Do();
        Assert.AreEqual(1, indicator);
    }
}

このコードは、変数がまだスコープ内にあり、インスタンスへの強い参照があるにAssert.Fail()もかかわらず、行のリリースモードでのみ失敗するため、WeakReferenceはまだ死んではなりません。relay

UPD:少し明確にするために:私はそれが「最適化」できることを理解しています。indicatorしかし、この最適化変数に応じて、0または1値があります。つまり、実際に目に見える動作の変化があります。

UPD2:C#言語仕様、セクション3.9から

オブジェクトまたはその一部に、デストラクタの実行以外の実行の継続によってアクセスできない場合、オブジェクトは使用されなくなったと見なされ、破棄の対象になります。C#コンパイラとガベージコレクタは、コードを分析して、オブジェクトへのどの参照が将来使用される可能性があるかを判断することを選択する場合があります。たとえば、スコープ内にあるローカル変数がオブジェクトへの唯一の既存の参照であるが、そのローカル変数がプロシージャ内の現在の実行ポイントからの実行の可能な継続で参照されない場合、ガベージコレクタは可能性があります(ただし、 )オブジェクトを使用されなくなったものとして扱う必要はありません。

技術的に言えば、このオブジェクトは実行を継続することでアクセスでき、アクセスできるため、「使用されなくなった」として扱うことはできません(実際、C#仕様は、コンパイラではなくCLRの側面であるため、弱参照については何も述べていません-コンパイラ出力結構です)。CLR/JITに関するメモリ管理情報を検索しようとします。

UPD3:CLRのメモリ管理に関する情報は次のとおりです-セクション「メモリの解放」:

...すべてのアプリケーションには一連のルートがあります。各ルートは、マネージヒープ上のオブジェクトを参照するか、nullに設定されます。アプリケーションのルートには、グローバルおよび静的オブジェクトポインター、スレッドのスタック上のローカル変数と参照オブジェクトパラメーター、およびCPUレジスタが含まれます。ガベージコレクターは、ジャストインタイム(JIT)コンパイラーとランタイムが維持するアクティブなルートのリストにアクセスできます。このリストを使用して、アプリケーションのルートを調べ、その過程で、ルートから到達可能なすべてのオブジェクトを含むグラフを作成します。

問題の変数は間違いなくローカル変数であるため、到達可能です。このように、この言及は非常に迅速で曖昧なので、より具体的な情報を見て本当にうれしいです。

UPD4:.NET Frameworkのソースから:

    // This method DOES NOT DO ANYTHING in and of itself.  It's used to
    // prevent a finalizable object from losing any outstanding references 
    // a touch too early.  The JIT is very aggressive about keeping an 
    // object's lifetime to as small a window as possible, to the point
    // where a 'this' pointer isn't considered live in an instance method 
    // unless you read a value from the instance.  So for finalizable
    // objects that store a handle or pointer and provide a finalizer that
    // cleans them up, this can cause subtle ----s with the finalizer
    // thread.  This isn't just about handles - it can happen with just 
    // about any finalizable resource.
    // 
    // Users should insert a call to this method near the end of a 
    // method where they must keep an object alive for the duration of that
    // method, up until this method is called.  Here is an example: 
    //
    // "...all you really need is one object with a Finalize method, and a
    // second object with a Close/Dispose/Done method.  Such as the following
    // contrived example: 
    //
    // class Foo { 
    //    Stream stream = ...; 
    //    protected void Finalize() { stream.Close(); }
    //    void Problem() { stream.MethodThatSpansGCs(); } 
    //    static void Main() { new Foo().Problem(); }
    // }
    //
    // 
    // In this code, Foo will be finalized in the middle of
    // stream.MethodThatSpansGCs, thus closing a stream still in use." 
    // 
    // If we insert a call to GC.KeepAlive(this) at the end of Problem(), then
    // Foo doesn't get finalized and the stream says open. 
    [System.Security.SecuritySafeCritical]  // auto-generated
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
    public static extern void KeepAlive(Object obj);

興味があれば、詳細についてはこちらをご覧ください

4

1 に答える 1

8

変数がスコープ内にある場合でも、将来のコードパスで変数にアクセスできなくなった場合、ランタイムは変数を自由に収集できます。これが、JIT最適化を使用してアセンブリをデバッグするときに、変数の値が現在スコープ内にある場合でも、変数の値が最適化されているというメッセージを表示できる理由です。

3.9自動メモリ管理の項目2を参照してください。

具体的には、

たとえば、スコープ内にあるローカル変数がオブジェクトへの唯一の既存の参照であるが、そのローカル変数がプロシージャ内の現在の実行ポイントからの実行の可能な継続で参照されない場合、ガベージコレクタは可能性があります(ただし、 )オブジェクトを使用されなくなったものとして扱う必要はありません。

これが重要なポイントです。オブジェクトへのすべての強力な参照(1つしかない)に到達できないため、オブジェクトにはアクセスできないと見なされます。C#仕様には、言語に関する情報と、コンパイルされたコードの実行方法に関する情報が含まれていることに注意してください。また、到達可能性はスコープによって定義されないことにも注意してください。仕様に記載されているように、コンパイラとランタイムが変数が将来のコードパスに存在しないと判断できる場合(つまり、変数がまったく参照されないか、のように到達不能と判断されたパスでのみ参照されるif(false))、変数は次のようになります。到達不能と見なされ、強力な参照としてカウントされません。

仕様のその特定の部分はWeakReference明示的に述べていませんが、そうする必要はありません。コンパイラに関する限り、その値を指すローカル変数は1つだけです。

WeakReferenceオブジェクトを引数として取る別のクラスです。コンパイラの観点から。そのクラスが渡された参照を保持しているかどうかについて信じる(または何らかの方法で仮定する)理由はありません。代わりに使用されたこのようなクラスがあるかどうかを検討してください。

public class MyClass
{
    public MyClass(object foo)
    {
        Console.WriteLine(foo);
    }
}

そして私のコードではこれを行いました:

var relay = new Relay();
...
var myClass = new MyClass(relay);

に割り当てた値への新しい強力な参照は導入しrelayMyClassいません。その参照を保持していないためです。強力な参照としてカウントされないWeakReferenceオブジェクトへの参照を提供するように設計された「特別な」クラスであるという事実は、コンパイラーに関する限り無関係です。

到達可能性はスコープによって定義されません。問題の変数(値ではない)が将来のコードパスにあるかどうかによって定義されます。relay関数の後半にはどのような形式でも存在しないため、変数(およびオブジェクトへの参照)は到達不能であり、収集に適格であると見なされます。これが、フラグがアセンブリレベルに存在する理由です。DisableOptimizationsこれにより、ランタイムは、変数がスコープから外れるまで待機してから、コレクションの対象になり、デバッガーにアクセスできるようになります。

于 2012-08-29T15:07:40.523 に答える