.Net에서 약한 참조를 언제 사용합니까?


56

나는 개인적으로 .Net에서 WeakReference 유형을 사용해야하는 상황을 직접 보지 못했지만 캐시에서 사용해야한다고 생각합니다. Jon Harrop 박사는 질문 에 대한 답변 에서 캐시에서 WeakReferences를 사용하는 것에 대해 매우 좋은 사례를 제시 했습니다 .

또한 AS3 개발자가 메모리 공간을 절약하기 위해 약한 참조를 사용하는 것에 대해 이야기하지만 종종 의도 한 목표를 달성하지 않고도 복잡성을 추가하는 것으로 보이며 런타임 동작은 예측할 수 없습니다. 너무 많은 사람들이 단순히 그것을 포기하고 대신 메모리 사용을보다 신중하게 관리하고 코드를 최적화하여 메모리를 덜 사용합니다 (또는 더 많은 CPU 사이클과 더 작은 메모리 풋 프린트를 절충하십시오).

Jon Harrop 박사는 또한 .Net 약한 참조는 부드럽 지 않으며 gen0에는 약한 참조의 공격적인 컬렉션이 있다고 그의 대답에서 지적했습니다. MSDN 에 따르면 약한 참조가 길면 개체를 다시 만들 수 있습니다 but the state of the object remains unpredictable..

이러한 특성을 감안할 때 약한 참조가 유용 할 수있는 상황을 생각할 수 없으며 누군가 나를 밝게 할 수 있습니까?


3
당신은 이미 그것의 잠재적 용도를 설명했습니다. 물론 이러한 상황에 접근하는 다른 방법이 있지만 고양이를 피부에 대는 방법은 여러 가지가 있습니다. "방문 할 때 항상 WeakReference를 사용해야합니다"라는 방탄을 찾고 있다면 그 중 하나를 찾을 수있을 것입니다.

2
@ itsme86-방탄 사용 사례를 찾고 있지 않습니다. 약한 참조가 적합하고 의미가있는 것입니다. 약한 참조 그렇게 열심히 수집되기 때문에 예를 들어 캐시를 사용하는 경우, 당신이 사용 가능한 메모리의 많음이있을 때 캐시도 그리워 이상을 일으킬 것

4
나는 이것이 많은 표를 끝내기 위해 조금 실망했다. b4 "스택 오버플로는 포럼이 아닙니다"에서 이에 대한 답변이나 토론을 보지 않겠습니다.
ta.speot.is는

@theburningmonk 메모리 게인에 대한 대가로 오프셋입니다. 오늘날의 프레임 워크에서는 캐시를 구현할 때에도 누구나 쉽게 사용할 수있는 포괄적 인 캐싱 시스템이 있기 때문에 WeakReference 도구를 사용하는 사람은 누구도 없을 것입니다.

다음 은 그것들을 사용하는 것에 대한 당혹스럽고 지나치게 복잡한 예입니다 (ta.speot.가 아래에 설명하는 약한 이벤트 패턴에 대해)
Benjol

답변:


39

나는 실제로 개인적으로 나에게 일어난 다음 세 가지 실제 시나리오에서 약한 참조의 합법적 인 실제 적용을 발견했습니다.

애플리케이션 1 : 이벤트 핸들러

당신은 기업가입니다. 귀사는 WPF 용 스파크 라인 제어를 판매합니다 . 판매는 크지 만 지원 비용으로 인해 사망하고 있습니다. 스파크 라인으로 가득 찬 화면을 스크롤 할 때 너무 많은 고객이 CPU 호깅 및 메모리 누수에 대해 불평하고 있습니다. 문제는 앱에서 새로운 스파크 라인을 생성하지만 데이터 바인딩으로 인해 오래된 스파크 라인이 가비지 수집되는 것을 방해한다는 것입니다. 너 뭐하니?

데이터 바인딩만으로는 더 이상 컨트롤이 가비지 수집되는 것을 막지 않도록 데이터 바인딩과 컨트롤 사이에 약한 참조를 도입하십시오. 그런 다음 데이터 바인딩을 수집 할 때 데이터 바인딩을 해제하는 종료자를 컨트롤에 추가하십시오.

응용 프로그램 2 : 가변 그래프

당신은 다음 존 카맥입니다. Tim Sweeney의 게임을 Nintendo Wii처럼 보이게하는 계층 적 세분화 표면에 대한 독창적 인 새로운 그래프 기반 표현을 발명했습니다. 분명히 나는 그것이 어떻게 작동하는지 정확하게 말하지 않을 것입니다. 그러나 그것은 모두 정점의 이웃이에서 찾을 수있는이 가변 그래프의 중심에 있습니다 Dictionary<Vertex, SortedSet<Vertex>>. 플레이어가 돌아 다니면서 그래프의 토폴로지가 계속 변경됩니다. 단 하나의 문제점이 있습니다. 데이터 구조가 도달 할 수없는 하위 그래프를 흘리며이를 제거해야합니다. 그렇지 않으면 메모리가 누출됩니다. 운 좋게도 당신은 천재이므로 도달 할 수없는 하위 그래프를 찾고 수집하도록 특별히 설계된 알고리즘 클래스가 있다는 것을 알고 있습니다 : 가비지 수집기! 당신 은 그 주제에 관한 Richard Jones의 훌륭한 논문을 읽었습니다.그러나 그것은 임박한 마감일에 대해 당황하고 걱정하게합니다. 너 뭐하니?

간단히 Dictionary해시 테이블을 약한 해시 테이블 로 바꾸면 기존 GC를 피기 백하고 도달 할 수없는 하위 그래프를 자동으로 수집 할 수 있습니다! 페라리를 통해 잎으로 돌아온다.

응용 프로그램 3 : 나무 장식

당신은 키보드의 자전거 타는 방의 천장에 매달려 있습니다. 누군가가 당신을 발견하기 전에 약간의 빅 데이터를 탐색하는 데 60 초가 걸립니다. AST의 조각을 분석 한 후 GC에 의존하는 멋진 스트림 기반 파서를 준비했습니다. 그러나 각 AST에 추가 메타 데이터 Node가 필요하고이를 빠르게 사용해야 한다는 것을 알고 있습니다 . 너 뭐하니?

a Dictionary<Node, Metadata>를 사용하여 메타 데이터를 각 노드와 연결할 수 있지만,이를 지우지 않는 한 사전에서 이전 AST 노드에 대한 강력한 참조는 활성 상태를 유지하고 메모리를 누출시킵니다. 이 솔루션은 약한 해시 테이블 로, 키에 대한 약한 참조 만 유지하고 키에 도달 할 수 없을 때 가비지가 키-값 바인딩을 수집합니다. 그런 다음 AST 노드에 도달 할 수 없게되면 가비지 수집되고 해당 키-값 바인딩이 사전에서 제거되어 해당 메타 데이터에 도달 할 수 없으므로 수집됩니다. 그런 다음 메인 루프가 끝난 후해야 할 일은 에어 가드를 통해 위로 밀어 올려 경비원이 들어올 때와 같이 교체해야한다는 것을 기억하십시오.

실제로 나에게 일어난 이러한 세 가지 실제 응용 프로그램 에서 GC가 가능한 한 적극적으로 수집 하기를 원 했습니다. 이것이 합법적 인 응용 프로그램 인 이유입니다. 다른 사람들은 모두 잘못입니다.


2
도달 할 수없는 하위 그래프에주기가 포함되어 있으면 응용 프로그램 2에 대한 약한 참조가 작동하지 않습니다. 약한 해시 테이블에는 일반적으로 키에 대한 약한 참조가 있지만 값에 대한 강한 참조가 있기 때문입니다. 키에 여전히 도달 할 수있는 동안에 만 값에 대한 강력한 참조를 유지하는 해시 테이블이 필요합니다 (> ConditionalWeakTable.NET에서 ephemerons 참조 ).
Daniel

@Daniel GC가 도달 할 수없는주기를 처리 할 수 ​​있지 않습니까? 이것은 어떻게 것 없는 강한 참조의 연결할 수없는 사이클이 때 수집 될 것이다 수집?
binki

오, 내 생각에 방금 ConditionalWeakTable응용 프로그램 2와 3이 사용하는 반면 다른 게시물의 일부 사람들은 실제로 사용 한다고 가정했습니다 Dictionary<WeakReference, T>. 이유는 모릅니다. WeakReference어떤 방식 으로든 어떤 키로도 액세스 할 수없는 값을 가진 항상 null 값으로 끝납니다 . 리딕.
binki

@binki : "GC가 도달 할 수없는주기를 처리 할 수 ​​없습니까? 도달 할 수없는 강력한 참조주기가 수집 될 때 어떻게 수집되지 않습니까?" 다시 만들 수없는 고유 한 개체에 대한 사전이 있습니다. 키 객체 중 하나에 도달 할 수없는 경우 가비지 수집 될 수 있지만 사전의 해당 값은 이론적으로는 도달 할 수 없다고 생각할 수 없습니다. 일반 사전은 이에 대한 강력한 참조를 유지하므로이를 유지합니다. 따라서 약한 사전을 사용하십시오.
Jon Harrop

@Daniel : "도달 할 수없는 하위 그래프에주기가 포함 된 경우 약한 참조는 응용 프로그램 2에서 작동하지 않습니다. 약한 해시 테이블에는 일반적으로 키에 대한 약한 참조가 있지만 값에 대한 강한 참조가 있기 때문입니다. 해시 테이블이 필요합니다. "키에 여전히 도달 할 수있는 동안에 만 값에 대한 강력한 참조를 유지합니다." 예. GC가 자체적으로 수집하도록 포인터로 가장자리를 사용하여 그래프를 직접 인코딩하는 것이 좋습니다.
Jon Harrop

19

이러한 특성을 감안할 때 약한 참조가 유용 할 수있는 상황을 생각할 수 없으며 누군가 나를 밝게 할 수 있습니까?

Microsoft 문서 약한 이벤트 패턴 .

응용 프로그램에서, 이벤트 소스에 첨부 된 핸들러는 핸들러를 소스에 첨부 한 리스너 오브젝트와 함께 소멸되지 않을 수 있습니다. 이 상황은 메모리 누수로 이어질 수 있습니다. WPF (Windows Presentation Foundation)에는 특정 이벤트에 대한 전용 관리자 클래스를 제공하고 해당 이벤트에 대한 리스너에 인터페이스를 구현하여이 문제를 해결하는 데 사용할 수있는 디자인 패턴이 도입되었습니다. 이 디자인 패턴을 약한 이벤트 패턴이라고합니다.

...

약한 이벤트 패턴은이 메모리 누수 문제를 해결하도록 설계되었습니다. 약한 이벤트 패턴은 리스너가 이벤트를 등록해야 할 때마다 사용할 수 있지만 리스너는 등록을 취소 할시기를 명시 적으로 알지 못합니다. 약한 이벤트 패턴은 소스의 객체 수명이 리스너의 유용한 객체 수명을 초과 할 때마다 사용될 수 있습니다. 약한 이벤트 패턴을 사용하면 리스너가 리스너의 객체 수명 특성에 영향을주지 않고 이벤트를 등록하고 수신 할 수 있습니다. 실제로 소스의 암시 적 참조는 리스너가 가비지 콜렉션에 적합한 지 여부를 판별하지 않습니다. 참조는 약한 참조이므로 약한 이벤트 패턴 및 관련 API의 이름 지정. 리스너는 가비지 수집 또는 소멸 될 수 있으며 소스는 현재 파괴 된 오브젝트에 대한 수집 불가능한 핸들러 참조를 유지하지 않고 계속할 수 있습니다.


해당 URL은 '이 주제를 더 이상 사용할 수없는'최신 .NET 버전 (현재 4.5)을 선택합니다. 대신 .NET 4.0을 선택하면 작동합니다 ( msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx )
maxp

13

이것을 먼저 꺼내서 다시 보자.

약한 참조는 개체에 탭을 유지하려고 할 때 유용하지만 개체가 수집되는 것을 막기 위해 관찰을 원하지는 않습니다.

처음부터 시작하겠습니다.

의도하지 않은 공격에 대해서는 사전에 사과하지만, 잠재 고객에게 절대 말할 수 없기 때문에 잠시 동안 "Dick and Jane"수준으로 돌아가겠습니다.

따라서 객체를 얻었을 때 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 루트 인 객체를 가지고 있습니다 (자체적으로 구체적으로 말하면이 응용 프로그램을 실행하는 앱 도메인에 직접 체인을 통해 루팅되지만 두 가지 방법이 있습니다) 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();

이제, 한 Foo번에 한 번만 f언급했던 객체에 대한 "하드"참조가 더 이상 없기 때문에 수집 WeakReference할 수 있습니다.

좋은 사용 사례 :

  • 이벤트 처리기 (이 내용을 먼저 읽으십시오 : C #의 약한 이벤트 )

  • "재귀 참조"를 유발하는 상황이 있습니다 (예 : 객체 A는 객체 B를 의미하며 객체 A는 "메모리 누수"라고도 함). (편집 : derp, 물론 이것은 사실이 아님)

  • 객체 모음에 무언가를 "브로드 캐스트"하고 싶지만, 객체를 살아있는 것으로 유지하고 싶지는 않습니다. a List<WeakReference>는 쉽게 유지 관리 할 수 ​​있으며, 위치를 제거하여 정리할 수도 있습니다.ref.Target == null


1
두 번째 사용 사례와 관련하여 가비지 수집기는 순환 참조를 잘 처리합니다. "개체 A는 개체 A를 의미하는 개체 B를 의미합니다"는 확실히 메모리 누수가 아닙니다.
Joe Daley

@JoeDaley 동의합니다. .NET GC는 마크 앤 스윕 알고리즘을 사용하여 (이것을 올바르게 기억한다고 생각합니다) 수집 할 모든 객체를 표시 한 다음 "루트"(스택의 객체 참조, 정적 객체)의 참조를 따르고 수집을 위해 객체를 표시 해제합니다 . 순환 참조가 존재하지만 루트에서 액세스 할 수있는 오브젝트가없는 경우 오브젝트는 콜렉션으로 표시되지 않으므로 콜렉션에 적합합니다.
ta.speot.is은 (는)

1
@JoeDaley-물론 둘 다 맞습니다. 끝까지 그것을 서두르고있었습니다 ... 나는 그것을 편집 할 것입니다.
JerKimball

4

여기에 이미지 설명을 입력하십시오

사용자가 소프트웨어를 오랫동안 실행하는 것이 점점 더 많은 메모리를 사용하고 다시 시작할 때까지 느리게 느려지는 경향이 있음을 알면서도 실제로 추적하기 어려운 논리적 누수처럼? 난 아니야

사용자가 위의 응용 프로그램 리소스 제거 요청시 다음 Thing2과 같은 이벤트를 제대로 처리하지 못하면 어떻게되는지 고려하십시오 .

  1. 포인터
  2. 강력한 참조
  3. 약한 참조

... 그리고 이러한 실수 중 하나가 테스트 중에 잡히고 스텔스 전투기 버그처럼 레이더 아래에서 날지 않을 것입니다. 공유 소유권은 대부분 무의미한 아이디어입니다.


1

좋은 효과를 내기 위해 사용 된 약한 참조의 매우 예시적인 예는 ConditionalWeakTable 이며, 이는 DLR에서 (다른 곳에서) 추가 "멤버"를 객체에 첨부하는 데 사용됩니다.

테이블이 개체를 살아있는 상태로 유지하고 싶지 않습니다. 이 개념은 약한 참조 없이는 작동 할 수 없었습니다.

그러나 약한 참조는 버전 1.1 이후 .NET의 일부이기 때문에 약한 참조에 대한 모든 용도는 언어에 추가 된 지 오래 된 것 같습니다. 그것은 당신이 추가하고 싶은 것 같아서 결정 론적 파괴의 부족이 언어 기능에 관한 한 코너로 돌아 가지 않을 것입니다.


실제로 테이블은 약한 참조의 개념을 사용하지만 WeakReference상황이 훨씬 더 복잡하기 때문에 실제 구현에는 유형이 포함되지 않습니다 . CLR에 의해 노출 된 다른 기능을 사용합니다.
GregRos

-2

C #으로 캐시 계층을 구현 한 경우 데이터를 캐시에 약한 참조로 넣는 것이 훨씬 좋습니다. 캐시 계층 성능을 개선하는 데 도움이 될 수 있습니다.

접근 방식이 세션 구현에도 적용될 수 있다고 생각하십시오. 대부분의 경우 세션은 오래 지속되는 개체이므로 새로운 사용자를위한 메모리가없는 경우가있을 수 있습니다. 이 경우 다른 사용자 세션 객체를 삭제 한 다음 OutOfMemoryException을 발생시키는 것이 훨씬 좋습니다.

또한 응용 프로그램에 큰 객체 (일부 큰 조회 테이블 등)가있는 경우에는 거의 사용하지 않아야하며 이러한 객체를 다시 만드는 것은 비용이 많이 드는 절차가 아닙니다. 그런 다음 실제로 필요할 때 메모리를 비울 수있는 주 참조와 같은 것이 좋습니다.


5
그러나 약한 참조의 문제 (참조 한 답변 참조)는 매우 열심히 수집되며 컬렉션은 메모리 공간의 가용성과 관련이 없다는 것입니다. 따라서 메모리에 부담이 없을 때 더 많은 캐시 누락이 발생합니다.

1
그러나 큰 개체에 대한 두 번째 요점에 대해서는 MSDN 문서에 따르면 약한 참조가 길면 개체를 다시 만들 수 있지만 상태는 예측할 수 없습니다. 매번 처음부터 다시 작성하려는 경우 함수 / 메소드를 호출하여 필요할 때 작성하고 임시 인스턴스를 반환 할 때 약한 참조를 사용하는 것이 왜 어떻습니까?

캐싱이 도움이되는 한 가지 상황이 있습니다. 변경 불가능한 객체를 자주 생성하는 경우 많은 것이 동일 할 것입니다 (예 : 많은 중복이있을 것으로 예상되는 파일에서 많은 행을 읽는 경우) 각 문자열이 새 객체로 생성됩니다 그러나 행이 참조가 이미 존재하는 다른 행과 일치하면 새 인스턴스가 중단되고 기존 인스턴스에 대한 참조가 대체되는 경우 메모리 효율성이 향상 될 수 있습니다. 이 대체는 다른 참조가 어쨌든 유지되므로 유용합니다. 코드가 아닌 경우 새 코드를 유지해야합니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.