.Netで弱い参照を使用する場合


56

個人的に、.NetでWeakReference型を使用する必要がある状況に遭遇したことはありませんが、一般的な信念は、キャッシュで使用する必要があるということです。博士ジョン・ハロップは、彼の中にキャッシュ内WeakReferencesの使用に対して非常に良いケース与えた答えこの質問を。

また、AS3開発者がメモリフットプリントを節約するために弱い参照を使用することについて話すことをよく耳にしましたが、私が持っていた会話に基づいて、意図した目標を必ずしも達成することなく複雑さを追加するようであり、ランタイムの動作はかなり予測不可能です。そのため、多くの人は単純にそれをあきらめ、代わりにメモリ使用量をより慎重に管理する/コードを最適化してメモリ集約度を下げます(またはCPUサイクルを増やしてメモリフットプリントを小さくします)。

Jon Harrop博士はまた、.Net弱参照はソフトではなく、gen0には弱参照の積極的なコレクションがあると彼の答えで指摘しました。MSDNによると、長い弱参照はオブジェクトを再作成する可能性を与えてくれますbut the state of the object remains unpredictable.

これらの特性を考えると、弱い参照が役立つ状況を考えることはできません。おそらく誰かが私を啓発できるでしょうか?


3
あなたはすでにそれの潜在的な用途を概説しました。もちろん、これらの状況にアプローチする方法は他にもありますが、猫の皮を剥ぐ方法は複数あります。防弾「Xの場合は常にWeakReferenceを使用する必要があります」を探している場合、私はあなたがそれを見つけることを疑います。

2
@ itsme86-防弾ユースケースを探しているのではなく、弱参照が適切であり、理にかなっているユースケースだけを探しています。インスタンスのキャッシュ使用の場合、弱い参照はとても熱心に、ちょうどあなたが使用可能なメモリをたくさん持っている場合でも、複数のキャッシュミスを引き起こすことが起こって収集しているため

4
私はこれがいくつかの票を獲得することに少し失望しています。これについての答えや議論を見るのを気にしないでしょう(b4の「Stack Overflowはフォーラムではありません」)。
ta.speot.is

@theburningmonkこれは、メモリゲインの見返りのオフセットです。今日のフレームワークでは、包括的なキャッシングシステムがすぐに利用できるため、キャッシュを実装する場合でも、誰もがWeakReferenceツールに直接アクセスすることは疑わしいです。

以下に、それらを使用した非常に複雑な例を示します(ta.speot.isで後述する弱いイベントパターンの場合)
Benjol

回答:


39

私が実際に個人的に起こった次の3つの実際のシナリオで、弱参照の合法的な実用的なアプリケーションを見つけました。

アプリケーション1:イベントハンドラー

あなたは起業家です。あなたの会社は、WPFのスパークラインコントロールを販売しています。売り上げは素晴らしいですが、サポート費用がかかります。スパークラインでいっぱいの画面をスクロールすると、CPUが大量に消費され、メモリリークが発生するという不満を抱いているお客様が多すぎます。問題は、アプリが表示されるときに新しいスパークラインを作成しますが、データバインディングが古いものをガベージコレクションから防ぐことです。職業はなんですか?

データバインディングとコントロールの間に弱い参照を導入して、データバインディングだけでコントロールのガベージコレクションが妨げられないようにします。次に、ファイナライザをコントロールに追加して、収集時にデータバインディングを破棄します。

アプリケーション2:可変グラフ

あなたは次のジョン・カーマックです。ティムスウィーニーのゲームを任天堂Wiiのように見せるための、階層的なサブディビジョンサーフェスの新しいグラフベースの表現を発明しました。明らかに、それがどのように機能するか正確に説明するつもりはありませんが、頂点の近傍がで見つかるこの可変グラフを中心にしていDictionary<Vertex, SortedSet<Vertex>>ます。プレーヤーが走り回るにつれて、グラフのトポロジーは変化し続けます。問題は1つだけです。データ構造が実行中に到達不能なサブグラフを流しているため、それらを削除する必要があるか、メモリリークが発生します。幸運なことにあなたは天才なので、到達不能なサブグラフを見つけて収集するために特別に設計されたアルゴリズムのクラスがあることを知っています:ガベージコレクター!あなたはリチャード・ジョーンズの主題に関する優れたモノグラフを読みますしかし、それはあなたを困惑させ、あなたの差し迫った締め切りを心配させたままにします。職業はなんですか?

Dictionary弱いハッシュテーブルに置き換えるだけで、既存のGCに便乗し、到達不能なサブグラフを自動的に収集させることができます!フェラーリの広告のリーフィングに戻ります。

アプリケーション3:木を飾る

あなたは、キーボードのある円筒形の部屋の天井からぶら下がっています。誰かがあなたを見つける前に、いくつかの大きなデータをふるいにかけるのに60秒かかります。GCに依存して分析されたASTのフラグメントを収集する美しいストリームベースのパーサーを用意しました。しかし、各ASTに追加のメタデータNodeが必要であり、高速に必要であることに気付きます。職業はなんですか?

を使用しDictionary<Node, Metadata>てメタデータを各ノードに関連付けることができますが、クリアしない限り、ディクショナリから古いASTノードへの強力な参照はそれらを存続させ、メモリをリークします。解決策は、キーへの弱い参照のみを保持し、キーが到達不能になったときにキーと値のバインディングをガベージコレクションする弱いハッシュテーブルです。次に、ASTノードが到達不能になると、それらはガベージコレクションされ、キーと値のバインディングがディクショナリから削除され、対応するメタデータも到達不能になるため、収集されます。メインループが終了した後、あなたがしなければならないのは、警備員が入ってきたときにそれを交換することを忘れずに、通気口を通して上にスライドすることです。

実際に私に起こったこれら3つの実世界のアプリケーションすべてで、GCを可能な限り積極的に収集したかったことに注意してください。これらが正当なアプリケーションである理由です。他の誰もが間違っています。


2
到達不能なサブグラフにサイクルが含まれている場合、弱い参照はアプリケーション2では機能しません。これは、通常、弱いハッシュテーブルにはキーへの弱い参照がありますが、値への強い参照があるためです。キーがまだ到達可能な間だけ値への強い参照を維持するハッシュテーブルが必要になります->エフェメロン(ConditionalWeakTable.NET)を参照してください。
ダニエル

@Daniel GCは到達不能なサイクルを処理できるはずではありませんか?強力な参照の到達不能なサイクルが収集される場合、これどのように収集されないのでしょうか?
ビンキ

ああ、そうだと思う。ConditionalWeakTable他の投稿の何人かが実際に使用するのに対して、私はそれがアプリケーション2と3が使用するものだと思いましたDictionary<WeakReference, T>。理由WeakReferenceはわかりません。どのように操作しても、どのキーからもアクセスできない値が常に大量のnullになってしまいます。リディク。
ビンキ

@binki:「GCは到達不能なサイクルを処理できるはずではありませんか?強い参照の到達不能なサイクルが収集される場合、これはどのように収集されないのですか?」再作成できない一意のオブジェクトをキーとする辞書があります。キーオブジェクトの1つが到達不能になると、ガベージコレクションされる可能性がありますが、辞書の対応する値は、通常の辞書がそのオブジェクトへの強い参照を保持しているため、理論的に到達不能とは見なされません。したがって、弱い辞書を使用します。
ジョンハロップ

@Daniel:「到達不能なサブグラフにサイクルが含まれている場合、アプリケーション2に対して弱い参照は機能しません。これは、通常、弱いハッシュテーブルにはキーへの弱い参照がありますが、値への強い参照があるためです。キーがまだ到達可能である間だけ値への強い参照を維持します。」はい。GCがそれ自体を収集するように、ポインターとしてグラフを直接エンコードする方がよいでしょう。
ジョンハロップ

19

これらの特性を考えると、弱い参照が役立つ状況を考えることはできません。おそらく誰かが私を啓発できるでしょうか?

マイクロソフトのドキュメントWeak Event Patterns

アプリケーションでは、イベントソースにアタッチされたハンドラーが、ハンドラーをソースにアタッチしたリスナーオブジェクトと連携して破棄されない可能性があります。この状況は、メモリリークにつながる可能性があります。Windows Presentation Foundation(WPF)は、特定のイベントに専用のマネージャークラスを提供し、そのイベントのリスナーにインターフェイスを実装することにより、この問題に対処するために使用できるデザインパターンを導入します。この設計パターンは、弱いイベントパターンとして知られています。

...

弱いイベントパターンは、このメモリリークの問題を解決するように設計されています。弱いイベントパターンは、リスナーがイベントに登録する必要があるときはいつでも使用できますが、リスナーは登録解除のタイミングを明示的に知りません。弱いイベントパターンは、ソースのオブジェクトライフタイムがリスナーの有用なオブジェクトライフタイムを超える場合にも使用できます。(この場合、有用性はユーザーが決定します。)弱いイベントパターンを使用すると、リスナーはオブジェクトのライフタイム特性に影響を与えることなく、イベントを登録および受信できます。実際には、ソースからの暗黙の参照は、リスナーがガベージコレクションの対象であるかどうかを判断しません。参照は弱い参照であるため、弱いイベントパターンと関連するAPIの命名。リスナーはガベージコレクションされるか、破棄される可能性があり、ソースは破棄されたオブジェクトへの収集不可能なハンドラー参照を保持せずに続行できます。


このURLは、「このトピックはもう利用できません」最新の.NETバージョン(現在4.5)を自動的に選択します。.NET 4.0を選択すると、代わりに(作品msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx
をmaxP

13

これを最初に出して戻ってみましょう。

WeakReferenceは、オブジェクトのタブを保持したいが、そのオブジェクトが収集されないように観察したくない場合に便利です。

それでは最初から始めましょう:

-意図しない攻撃については事前におapび申し上げますが、しばらくの間は「ディックとジェーン」レベルに戻ります。

そのため、オブジェクトを取得したらX-インスタンスとして指定しましょうclass Foo-それ自身で生きることはできません(ほとんど真)。「人は島ではありません」と同じように、オブジェクトを島に昇格させる方法はいくつかありますが、CLRのGCルートと呼ばれています。GCルートであること、またはGCルートへの接続/参照の確立されたチェーンを持つことは、基本的Foo x = new Foo()にガベージコレクションを取得するかどうかを決定するものです。

ヒープまたはスタックウォーキングによってGCルートに戻ることができない場合、事実上孤立しており、次のサイクルでマーク/収集される可能性があります。

この時点で、いくつかの恐ろしい仕掛けの例を見てみましょう。

まず、私たちのFoo

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

かなり簡単です-スレッドセーフではないので、試してはいけませんが、アクティブなインスタンスの大まかな「参照カウント」と、それらがファイナライズされたときに減少します。

では、を見てみましょうFooConsumer

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

したがって、既にGCルートであるオブジェクト(具体的には、このアプリケーションを実行しているアプリドメインに直接チェーンを介してルートされますが、それは別のトピックです)には2つのメソッドがありますFooインスタンスにラッチする-テストしてみましょう:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

さて、上記から、一度参照されたオブジェクトfが「収集可能」になると期待しますか?

いいえ、現在その参照を保持している別のオブジェクトがあるためです- DictionaryそのSingleton静的インスタンス内。

わかりました、弱いアプローチを試みましょう:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

さて、--- Foothat-was-once- への参照を強打するfと、オブジェクトへの「ハード」参照はなくなるため、収集可能になります- WeakReference弱いリスナーによって作成されたものはそれを防ぎません。

良い使用例:

  • イベントハンドラー(最初にこれを読んでください:C#の弱いイベント

  • 「再帰的参照」を引き起こす状況があります(つまり、オブジェクトAはオブジェクトBを参照し、オブジェクトBはオブジェクトAを参照し、「メモリリーク」とも呼ばれます(編集:derp、もちろんこれはありません真実ではない)

  • オブジェクトのコレクションに何かを「ブロードキャスト」したいのですが、オブジェクトを生きたままにしたくないのです。a List<WeakReference>は簡単に維持でき、さらにどこを削除することで整理することもできます。ref.Target == null


1
2番目の使用例については、ガベージコレクターは循環参照を適切に処理します。「オブジェクトAはオブジェクトBを指し、オブジェクトBはオブジェクトAを指します」は間違いなくメモリリークではありません。
ジョーデイリー

@JoeDaley同意します。.NET GCは、マークアンドスイープアルゴリズムを使用します(これを正しく思い出すと思います)。すべてのオブジェクトをコレクションにマークし、「ルート」からの参照(スタック上のオブジェクトの参照、静的オブジェクト)に従います。 。循環参照は存在するが、ルートからアクセスできるオブジェクトがない場合、オブジェクトはコレクションのマークが解除されないため、コレクションの対象になります。
ta.speot.is

1
@JoeDaley-あなたは両方とも、もちろん正しいです-最後に向かって急いでいた...私はそれを編集します。
JerKimball

4

ここに画像の説明を入力してください

論理的なリークのように、ユーザーがソフトウェアを長時間実行すると、メモリをより多く消費し、再起動するまでゆっくりと遅くなる傾向があることに気付きがちですが、追跡が非常に困難ですか?しません。

上記のアプリケーションリソースの削除をユーザーが要求したときに、次のThing2ようなイベントを適切に処理できなかった場合にどうなるかを検討してください。

  1. ポインタ
  2. 強参照
  3. 弱い参照

...そして、これらのミスのどれがテスト中にキャッチされる可能性があり、どのステルス戦闘機のバグのようにレーダーの下で飛ぶことはありませんか?所有権の共有は、ほとんどの場合、無意味なアイデアです。


1

良い効果が得られる弱参照の非常にわかりやすい例は、ConditionalWeakTableです。これは、DLRが(他の場所の中でも)追加の「メンバー」をオブジェクトにアタッチするために使用します。

テーブルがオブジェクトを存続させたくない場合。この概念は、弱い参照がなければ機能しませんでした。

しかし、弱参照はバージョン1.1以来.NETの一部であるため、弱参照の使用はすべて言語に追加されてからずっと後になったように思えます。追加したいもののように思えるので、言語機能に関する限り、決定論的な破壊がないからといってコーナーに戻ることはありません。


私は実際に、このテーブルでは弱参照の概念を使用していますが、実際の実装にはWeakReference状況がはるかに複雑であるため、型を含まないことを発見しました。CLRによって公開されるさまざまな機能を使用します。
グレッグロス

-2

キャッシュレイヤーをC#で実装している場合は、データを弱参照としてキャッシュに入れることをお勧めします。キャッシュレイヤーのパフォーマンスを向上させるのに役立ちます。

このアプローチは、セッションの実装にも適用できると考えてください。セッションはほとんどの場合、長生きするオブジェクトであるため、新しいユーザー用のメモリがない場合があります。その場合、OutOfMemoryExceptionをスローしてから、他のユーザーセッションオブジェクトを削除する方がはるかに優れています。

また、アプリケーションに大きなオブジェクト(大きなルックアップテーブルなど)がある場合、それを使用することはめったになく、そのようなオブジェクトの再作成はそれほど高価な手順ではありません。それから、本当に必要なときにあなたの記憶を解放する方法を持っている週参照のようにそれを持っている方がよい。


5
しかし、弱い参照の問題(参照した回答を参照)は、それらが非常に熱心に収集されており、収集がメモリ空間の可用性にリンクされていないことです。したがって、メモリに負荷がかかっていない場合は、キャッシュミスが多くなります。

1
しかし、大きなオブジェクトに関する2番目のポイントについて、MSDNのドキュメントでは、長い弱い参照を使用するとオブジェクトを再作成できますが、その状態は予測できないままであると述べています。毎回ゼロから再作成する場合、関数/メソッドを呼び出してオンデマンドで作成し、一時的なインスタンスを返すことができるのに、なぜ弱参照を使用するのですか?

キャッシングが役立つ状況が1つあります。不変オブジェクトを頻繁に作成する場合、その多くが同一になる(たとえば、多くの重複があると予想されるファイルから多くの行を読み取る)場合、各文字列は新しいオブジェクトとして作成されます、しかし、参照が既に存在する別の行と一致する場合、新しいインスタンスが放棄され、既存のインスタンスへの参照が置き換えられると、メモリ効率が向上する可能性があります。とにかく他の参照が保持されているため、この置換は便利です。コードでなかった場合、新しいものを保持する必要があります。
supercat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.