.NET'te zayıf referanslar ne zaman kullanılır?


56

Kişisel olarak .Net'te WeakReference türünü kullanmam gereken bir durumla karşılaşmadım, ancak popüler inancın önbelleklerde kullanılması gerektiği görünüyor. Dr Jon Harrop onun içinde önbelleklerdeki WeakReferences kullanımına karşı çok iyi bir durumda verdi cevabı için bu soruya.

Ayrıca, AS3 geliştiricilerinin bellek ayak izinden tasarruf etmek için zayıf referanslar kullanmaktan bahsettiğini ama duyduğum konuşmalara dayanarak, amaçlanan hedefe ulaşmadan mutlaka karmaşıklık kattığı görünmektedir ve çalışma zamanı davranışı tahmin edilemezdir. Öyle ki, çoğu kişi basitçe vazgeçmek yerine, bellek kullanımını daha dikkatli yönetin / kodlarını daha az bellek yoğunlaştırmak için kodlarını optimize edin (ya da daha fazla CPU döngüsü ve daha küçük bellek alanı kapsıyor).

Dr Jon Harrop da cevabında .Net zayıf referanslarının yumuşak olmadığını ve gen0'da agresif bir zayıf referans koleksiyonu olduğunu belirtti. MSDN'ye göre , uzun zayıf referanslar size bir nesneyi yeniden yaratma potansiyeli veriyor but the state of the object remains unpredictable.!

Bu özellikler göz önüne alındığında, zayıf referansların yararlı olabileceği bir durum düşünemiyorum, belki biri beni aydınlatıyor olabilir?


3
Zaten bunun için potansiyel kullanımları belirlediniz. Elbette bu durumlara yaklaşmanın başka yolları da var, ancak bir kedinin derisini yüzmenin birden fazla yolu var. Kurşun geçirmez arıyorsanız "X olduğunda her zaman bir WeakReference kullanmalısınız" arıyorsanız, birini bulacağınızdan şüpheliyim.

2
@ itsme86 - Kurşun geçirmez bir kullanım çantası aramıyorum, sadece zayıf referansların uygun ve mantıklı olduğu durumlar. Zayıf referanslar bu yüzden hevesle toplanır çünkü örneğin önbellek kullanım durumu, sadece bellek mevcut bol olduğunda önbellek bile özlüyor daha neden olacak

4
Biraz hayal kırıklığına uğradım, bu oyla kapanacak oylar alıyor. Bununla ilgili bir cevap ya da tartışma görmeyi umursamıyorum (b4'te "Yığın Taşması bir forum değildir").
ta.speot.is

@ theburningmonk Bu bellek kazancı karşılığında ötelemedir. Günümüz çerçevesinde, herkesin bir önbellek uygularken bile WeakReference aracı için doğrudan ulaşabileceği şüphelidir çünkü hazır önbellekleme sistemleri mevcuttur.

İşte , bunları kullanmanın utanç verici bir şekilde aşırı karmaşık bir örneği (ta.speot.is'in aşağıda tanımladığı Zayıf Olay Örneği için)
Benjol

Yanıtlar:


39

Kişisel olarak başıma gelen aşağıdaki üç gerçek dünya senaryosunda, zayıf referansların meşru pratik uygulamalarını buldum:

Uygulama 1: Olay işleyicileri

Sen bir girişimcisin. Şirketiniz WPF için bir kıvılcım çizgileri kontrolü satıyor . Satışlar harika ancak destek maliyetleri sizi öldürüyor. Çok fazla müşteri, kıvılcım dolu çizgilerle ekranlar arasında gezinirken CPU hogging ve bellek sızıntılarından şikayet ediyor. Sorun, uygulamalarının ortaya çıktıklarında yeni kıvılcım çizgileri yaratmasıdır ancak veri bağlama, eskilerin çöp toplanmasını engellemektedir. Ne yaparsın?

Veri bağlama ile kontrolünüz arasında zayıf bir referans verin, böylece tek başına veri bağlama artık kontrolünüzün çöp toplanmasını engellemeyecektir. Sonra kontrolünüze toplandığında veri bağlantısını parçalayan bir sonlandırıcı ekleyin.

Uygulama 2: Değişken grafikler

Sıradaki John Carmack'sin. Tim Sweeney oyunlarının bir Nintendo Wii gibi görünmesini sağlayan hiyerarşik alt bölme yüzeylerinin ingenius yeni bir grafik tabanlı gösterimini icat ettiniz. Açıkçası, size tam olarak nasıl çalıştığını anlatmayacağım ama hepsi bir köşenin komşularının içinde bulunabileceği bu değişken grafik üzerinde odaklanıyor Dictionary<Vertex, SortedSet<Vertex>>. Grafiğin topolojisi, oyuncu etrafta dolaşırken değişmeye devam ediyor. Tek bir sorun var: Veri yapınız çalıştırılırken erişilemeyen alt yazıları yayınlıyor ve bunları kaldırmanız gerekiyor, yoksa bellekte sızıntı olacak. Neyse ki sen bir dahisin, o yüzden özellikle ulaşılamaz alt yazıları bulmak ve toplamak için özel olarak tasarlanmış bir algoritma sınıfı olduğunu biliyorsun: çöp toplayıcıları! Richard Jones'un konuyla ilgili mükemmel monografını okudunuzama sizi şaşırtıyor ve yakın tarihiniz için endişeleniyor. Ne yaparsın?

Basitçe, sizin Dictionaryiçin zayıf bir karma tabloyu değiştirerek mevcut GC'yi geri çekebilir ve ulaşılamaz alt yazılarınızı otomatik olarak toplamasını sağlayabilirsiniz! Ferrari ilanlarına göre yapraklanmaya geri dönelim.

Uygulama 3: dekorasyon ağaçları

Klavyede bulunan dairesel bir odanın tavanından sarkıyorsunuz. Biri sizi bulana dek BÜYÜK VERİLERE göz atmak için 60 saniyen var. Analiz edildikten sonra AST'nin parçalarını toplamak için GC'ye dayanan güzel bir akış tabanlı ayrıştırıcı ile hazırlandınız. Ancak, her AST için fazladan meta verilere Nodeihtiyacınız olduğunu ve hızlı bir şekilde ihtiyacınız olduğunu biliyorsunuz. Ne yaparsın?

Dictionary<Node, Metadata>Meta verileri her düğümle ilişkilendirmek için a kullanabilirsiniz , ancak silmediğiniz sürece, sözlükten eski AST düğümlerine yapılan güçlü referanslar onları canlı tutar ve bellek sızdırmaz. Çözüm, anahtarlara yalnızca zayıf referansları tutan ve anahtar, erişilemez hale geldiğinde anahtar değer bağlamalarını toplayan zayıf bir karma tablodur . Daha sonra, AST düğümleri erişilemez hale geldikçe, bunlar çöp toplanır ve anahtar-değer bağlamaları, söz konusu meta verilere erişilemez halde bırakılarak sözlükten çıkarılır. Sonra, ana döngünüz sona erdikten sonra yapmanız gereken tek şey, güvenlik görevlisi gelirken değiştirmeyi hatırlayarak havalandırma deliğinden geri kaydırmaktır.

Başıma gelen bu gerçek dünya uygulamalarının üçünde de GC'nin mümkün olduğunca agresif bir şekilde toplanmasını istediğimi unutmayın . Bu yüzden bunlar meşru uygulamalar. Diğer herkes yanlış.


2
Ulaşılamayan alt yazılar döngü içeriyorsa, zayıf referanslar uygulama 2 için çalışmaz. Bunun nedeni, zayıf bir karma tablonun genellikle anahtarlara zayıf referanslara, ancak değerlere güçlü referanslara sahip olmasıdır. Sadece anahtar hala erişilebilirken değerlere güçlü referanslar sağlayan bir karma tabloya ihtiyacınız olacaktır -> bkz. Efemeronlar ( ConditionalWeakTable.NET'te).
Daniel

@Daniel GC erişilemez çevrimleri idare edemez mi? Bu nasıl olur değil güçlü referansların ulaşılamaz döngüsü sırasında toplanacak olur toplanacak?
binki

Oh, sanırım görüyorum. Ben sadece ConditionalWeakTable2 ve 3 numaralı uygulamaların kullanacağını, diğer yazılardaki bazı kişilerin gerçekte kullandıklarını varsaydım Dictionary<WeakReference, T>. Neden bilmiyorum - WeakReferencenasıl yaparsanız yapın bir anahtarla erişilemeyen değerlerle her zaman bir ton boş değer elde edersiniz. Ridik.
binki

@binki: "GC'nin ulaşılamaz çevrimleri ele alması gerekmiyor mu? Ulaşılamaz bir güçlü referanslar döngüsü toplandığında bu nasıl toplanmayacaktı?". Yeniden oluşturulamayan benzersiz nesnelere anahtarlanmış bir sözlüğünüz var. Anahtar nesnelerinizden biri erişilemez hale geldiğinde, çöp toplanabilir, ancak sözlükteki karşılık gelen değer teorik olarak erişilemez diye düşünülmez bile çünkü sıradan bir sözlük canlı tutulur. Demek zayıf bir sözlük kullanıyorsun.
Jon Harrop

@Daniel: "Eğer ulaşılamayan alt kısımlar döngü içeriyorsa, zayıf referanslar uygulama 2 için işe yaramaz. Bunun nedeni, zayıf bir karma tablo genellikle anahtarlara zayıf referanslara sahiptir, ancak değerlere güçlü referanslar. Bu bir karma tabloya ihtiyacınız olacaktır. sadece anahtar hala erişilebilir durumdayken değerlere güçlü referansları korur ". Evet. Grafiği, işaretçiler olarak doğrudan kenarlarla kodlamaktan muhtemelen daha iyi olursunuz, böylece GC onu kendisi toplar.
Jon Harrop

19

Bu özellikler göz önüne alındığında, zayıf referansların yararlı olabileceği bir durum düşünemiyorum, belki biri beni aydınlatıyor olabilir?

Microsoft belge Zayıf Olay Desenleri .

Uygulamalarda, olay kaynaklarına bağlı işleyicilerin, işleyiciyi kaynağa bağlayan dinleyici nesne ile koordineli bir şekilde imha edilmemesi mümkündür. Bu durum hafıza sızıntılarına neden olabilir. Windows Presentation Foundation (WPF), belirli olaylar için özel bir yönetici sınıfı sağlayarak ve bu etkinlik için dinleyicilere bir arabirim uygulayarak, bu sorunu gidermek için kullanılabilecek bir tasarım deseni sunar. Bu tasarım deseni zayıf olay deseni olarak bilinir.

...

Zayıf olay deseni bu bellek sızıntısı problemini çözmek için tasarlanmıştır. Zayıf olay deseni, bir dinleyicinin bir etkinliğe kaydolması gerektiğinde kullanılabilir, ancak dinleyici ne zaman kaydını sileceğini açıkça bilmiyor. Zayıf olay deseni, kaynağın nesne ömrü, dinleyicinin yararlı nesne ömrünü aştığında da kullanılabilir. (Bu durumda, faydalı sizin tarafınızdan belirlenir.) Zayıf olay deseni, dinleyicinin, nesnenin yaşam boyu özelliklerini hiçbir şekilde etkilemeden olayı kaydetmesine ve almasına izin verir. Aslında, kaynaktan alınan referans, dinleyicinin çöp toplama için uygun olup olmadığını belirlemez. Referans zayıf bir referanstır, bu nedenle zayıf olay modelinin ve ilgili API'lerin isimlendirilmesi. Dinleyici çöp toplanabilir veya başka bir şekilde tahrip olabilir ve kaynak, tahrip edilemeyen bir işleyici referanslarını şimdi tahrip edilmiş bir nesneye tutmadan devam edebilir.


Bu URL otomatik, 'bu konunun artık bulunmadığı' en yeni .NET sürümünü (şu anda 4.5) seçiyor. Bunun yerine .NET 4.0'ı seçmek işe
yarar

13

Önce bunu ortaya koyalım ve geri dönelim:

Bir Nesnede sekmeleri tutmak istediğinizde WeakReference kullanışlıdır, ancak gözlemlerinizin nesnenin toplanmasını önlemesini istemezsiniz.

Öyleyse baştan başlayalım:

- kasıtsız herhangi bir suç için önceden teknolojiler, ancak bir kimseye hiç kimseye söyleyemediğinden, bir anlığına "Dick and Jane" seviyesine geri döneceğim.

Öyleyse bir nesneniz olduğunda X- bunu bir örnek olarak belirleyelim class Foo- kendi başına yaşayamaz (çoğunlukla doğru); Aynı şekilde, "Hiçbir erkek bir ada değildir", bir nesnenin Adalığa terfi edebileceği birkaç yol vardır - bununla birlikte CLR konuşmasında GC kökü olarak adlandırılır. Bir GC Kökü olmak veya bir GC köküne köklü bir bağlantı / referans zincirine sahip olmak temel olarak Foo x = new Foo()çöpün toplanıp toplanmayacağını belirleyen şeydir .

Yığın veya yığın yürüyüşü yoluyla bazı GC köklerine geri gidemiyorsanız, etkili bir şekilde yetim kaldınız ve büyük olasılıkla bir sonraki döngüde işaretlenecek / toplanacaksınız.

Bu noktada, bazı korkunç içerikli örnekleri inceleyelim:

İlk olarak, bizim 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--);
    }
}

Oldukça basit - iş parçacığı güvenli değildir, bu yüzden bunu denemeyin, ancak sonlandırıldığında aktif örneklerin ve düşüşlerin kaba bir "referans sayısını" korur.

Şimdi bir bakalım 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");
    }
}

Bu yüzden zaten kendi GC kökünün bir nesnesine sahibiz (peki ... spesifik olarak, bu uygulamayı çalıştıran uygulama etki alanına doğrudan bir zincir yoluyla köklenecek, ancak bu iki yöntemi olan) bir Fooörneğe mandalla - hadi deneyelim:

// 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();

Şimdi, yukarıdan, bir zamanlar denilen nesnenin f“toplanabilir” olmasını bekler miydiniz ?

Hayır, çünkü artık ona referans tutan başka bir nesne var - Dictionaryo Singletonstatik durumda.

Tamam, zayıf yaklaşımı deneyelim:

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();

Şimdi, bir zamanlar olan -e Foo-bir kez oldu- referansımıza vurduğumuzda f, nesneye daha fazla "sert" referanslar kalmıyor, bu yüzden koleksiyon yapılabilir - WeakReferencezayıf dinleyicinin yaratması bunu engellemeyecek.

İyi kullanım durumları:

  • Olay işleyicileri (İlk önce bunu okumasına rağmen: C #'daki Zayıf Olaylar )

  • Bir “özyinelemeli referans” (yani, A nesnesi, aynı zamanda “Bellek Sızıntısı” olarak da adlandırılan A nesnesini ifade eder, A nesnesini ifade eder) neden olacağınız bir durumunuz var (düzenleme: derp, elbette bu doğru değil)

  • Bir şeyleri bir koleksiyona "yayınlamak" istiyorsunuz, ancak onları canlı tutan şey olmak istemiyorsunuz; a List<WeakReference>kolayca tutulabilir ve hatta çıkarılarak budanabilirref.Target == null


1
İkinci kullanım durumunuzla ilgili olarak, çöp toplayıcı dairesel referansları iyi idare eder. "nesne A, nesne A'ya işaret eder, A nesne anlamına gelir" kesinlikle bir bellek sızıntısı değildir.
Joe Daley

@JoeDaley Katılıyorum. .NET GC (bunu doğru şekilde hatırlıyorum) toplama için tüm nesneleri işaretleyen ve daha sonra toplama için nesneleri işaretleyen "kökler" den (yığıntaki nesnelerin referansları, statik nesneler) referansları izleyen bir işaretleme ve tarama algoritması kullanır. . Dairesel bir referans varsa, ancak nesnelerden hiçbirine kökten erişilemiyorsa, nesneler koleksiyon için işaretlenmez ve bu nedenle koleksiyon için uygun olur.
ta.speot.is

1
@JoeDaley - Siz ikiniz de elbette haklısınız - orada sona eriyordu ... Bunu düzenleyeceğim.
JerKimball

4

görüntü tanımını buraya girin

Kullanıcılar sadece yazılımınızı uzun süre çalıştırmanın daha fazla bellek alma ve yeniden başlatana kadar daha yavaş ve daha yavaş olma eğiliminde olduklarını fark ederken eğilimini izlemek zor olan mantıksal sızıntılar gibi mi? Yapmıyorum.

Kullanıcı yukarıdaki uygulama kaynağını kaldırmak istediğinde, aşağıdaki Thing2gibi bir olayı gerektiği gibi ele almazsa ne olacağını göz önünde bulundurun :

  1. İşaretçiler
  2. Güçlü Referanslar
  3. Zayıf Referanslar

... ve bu hatalardan hangisinin test sırasında yakalanabileceği ve hangisinin radarın altında gizli bir avcı uçağı gibi uçmayacağı ve hangisinin altında olacağı. Paylaşılan mülkiyet, çoğu zaman, saçma sapan bir fikirdir.


1

İyi etki için kullanılan zayıf referansların çok açıklayıcı bir örneği , DLR tarafından (diğer yerler arasında) nesnelere ek "üyeler" eklemek için kullanılan ConditionalWeakTable'dır .

Masanın nesneyi canlı tutmasını istemiyorsun. Bu kavram basitçe zayıf referanslar olmadan işe yaramadı.

Fakat bana göre, zayıf referansların tüm kullanımları dile eklendikten çok sonra geldi, çünkü zayıf referanslar 1.1 sürümünden beri .NET'in bir parçasıydı. Bu sadece eklemek isteyeceğiniz bir şey gibi gözüküyor, böylece deterministik yıkım eksikliği, dil özellikleri söz konusu olduğunda sizi bir köşeye sıkıştırmayacak.


Aslında, tablo zayıf referanslar kavramını kullanmasına rağmen, asıl uygulamanın WeakReference, durum çok daha karmaşık olduğu için türü içermediğini keşfettim . CLR tarafından sunulan farklı işlevler kullanır.
GregRos

-2

C # ile uygulanmış önbellek katmanınız varsa, verilerinizi de önbelleğe zayıf referanslar olarak koymak çok daha iyidir, önbellek katmanı performansınızı artırmanıza yardımcı olabilir.

Bu yaklaşımın oturum uygulamasına da uygulanabileceğini düşünün. Oturum, çoğu zaman uzun süre yaşayan bir nesne olduğundan, yeni kullanıcı için hafızanız olmadığı zaman bir durum olabilir. Bu durumda, başka bir kullanıcı oturumu nesnesini silmek ve sonra OutOfMemoryException atmak çok daha iyi olacaktır.

Ayrıca, uygulamanızda büyük bir nesne varsa (bazı büyük arama tablosu, vb.), Nadiren kullanılmalıdır ve böyle bir nesnenin yeniden oluşturulması çok pahalı bir işlem değildir. O zaman gerçekten ihtiyacınız olduğunda hafızanızı boşaltmanın bir yolunu bulmak bir hafta referansı gibi olsa iyi olur.


5
Ancak, zayıf referanslarla ilgili sorun (başvurduğum cevaba bakınız) çok istekli bir şekilde toplandıkları ve koleksiyonun bellek alanı mevcudiyeti ile bağlantılı olmadığıdır. Böylece, hafıza üzerinde baskı olmadığında daha fazla önbellek özeti ile karşılaşırsınız.

1
Ancak büyük nesneler hakkındaki ikinci noktanız için MSDN doc, uzun zayıf referansların bir nesneyi yeniden yaratmanıza izin verirken, durumunun tahmin edilemez kaldığını belirtir. Her seferinde sıfırdan baştan yaratmaya başlayacaksanız, neden talep üzerine oluşturmak ve geçici bir örnek döndürmek için bir işlev / yöntem çağırabiliyorsanız, neden zayıf bir referans kullanmaktan rahatsız olursunuz?

Önbelleğe almanın yararlı olduğu bir durum vardır: Eğer biri çoğu zaman aynı olacak (örneğin, birçok kopyaya sahip olması beklenen bir dosyadan birçok satır okuyarak) her biri yeni bir nesne olarak oluşturulacak, sık sık değişmez nesneler yaratacaksa ancak bir satır zaten bir referansın var olduğu başka bir satıra eşleşirse, yeni örnek terk edilirse ve önceden var olan örneğe yapılan bir referansın yerine kullanılırsa, hafıza verimliliği artırılabilir. Bu ikamenin faydalı olduğuna dikkat edin, çünkü diğer referans yine de tutuluyor. Kod değilse yenisini tutmalı.
supercat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.