Коли використовувати слабкі посилання в .Net?


56

Я особисто не стикався з ситуацією, коли мені потрібно було використовувати тип WeakReference в .Net, але, мабуть, вважається, що його слід використовувати в кешах. Д-р Джон Харроп висловив дуже хорошу справу проти використання WeakReferences у кешах у своїй відповіді на це запитання.

Я також часто чув, як розробники AS3 розмовляють про використання слабких посилань, щоб заощадити на пам’яті слід, але виходячи з розмов, які у мене були, це, здається, додає складності, не обов'язково досягаючи наміченої мети, а поведінка часу виконання досить непередбачувана. Настільки, що багато людей просто відмовляються від цього і замість цього більш обережно керують використанням пам'яті / оптимізують свій код, щоб бути менш об'ємним у пам’яті (або роблячи компроміс більшої кількості процесорних циклів і меншої кількості пам'яті).

У своїй відповіді д-р Джон Харроп також зазначив, що слабкі посилання .Net не є м'якими, а в gen0 існує агресивна колекція слабких посилань. Згідно з MSDN , довгі слабкі посилання дають вам потенціал відтворити об’єкт but the state of the object remains unpredictable.,!

Враховуючи ці характеристики, я не можу придумати ситуацію, коли слабкі посилання були б корисні, можливо, хтось міг би мене просвітити?


3
Ви вже окреслили потенційне використання для цього. Звичайно, існують й інші способи наблизитись до цих ситуацій, але існує спосіб, як зібрати кішку. Якщо ви шукаєте куленепробивний "ви завжди повинні використовувати WeakReference коли X", я сумніваюся, що ви знайдете його.

2
@ itsme86 - Я не шукаю незахищеного випадку використання, а лише тих, на які слабкі посилання добре підходять і мають сенс. Наприклад, випадок використання кешу, оскільки слабкі посилання настільки охоче зібрані, що це просто спричинить більше

4
Я трохи розчарований, що я отримую кілька голосів. Я був би не проти побачити відповідь чи якусь дискусію з цього приводу (в b4 "Переповнення стека - це не форум").
ta.speot.is

@theburningmonk Це компенсація в обмін на збільшення пам'яті. У сучасних рамках сумнівно, щоб хтось не звертався до інструменту WeakReference навіть при впровадженні кешу, оскільки є цілісні системи кешування.

Ось бентежно надмірно складний приклад їх використання (для Шаблону слабких подій, який описує ta.speot.is нижче)
Benjol

Відповіді:


39

Я знайшов легітимне практичне застосування слабких посилань у наступних трьох реальних сценаріях, які насправді траплялися особисто мені:

Додаток 1: Обробники подій

Ти підприємець. Ваша компанія продає контрольні іскрові лінії для WPF. Продажі великі, але витрати на підтримку вбивають вас. Занадто багато клієнтів скаржаться на вивільнення процесора та протікання пам'яті, коли вони прокручують екрани, сповнені іскрових ліній. Проблема полягає в тому, що їхній додаток створює нові іскрові лінії, коли вони потрапляють у поле зору, але прив'язка даних перешкоджає збиранню старих сміття. Що ти робиш?

Введіть слабке посилання між прив'язкою даних та вашим контролем, щоб лише прив'язка даних більше не заважала вашому контролю збирати сміття. Потім додайте у свій контроль фіналізатор, який порушує прив'язку даних, коли вони збираються.

Додаток 2: Змінні графіки

Ти наступний Джон Кармак. Ви придумали нове геніальне представлення на основі графіків ієрархічних поверхонь підрозділу, завдяки чому ігри Тіма Свіні виглядають як Nintendo Wii. Очевидно, я не збираюся точно розповідати вам , як це працює, але все це зосереджено на цьому змінному графіку, де сусіди вершини можна знайти в Dictionary<Vertex, SortedSet<Vertex>>. Топологія графіка постійно змінюється, коли гравець біжить навколо. Є лише одна проблема: ваша структура даних викидає недоступні підграграфи під час роботи, і вам потрібно їх видалити, або ви просочите пам'ять. На щастя, ви геній, тому ви знаєте, що існує клас алгоритмів, розроблений спеціально для пошуку та збору недосяжних підграфів: сміттєзбирачі! Ви прочитали чудову монографію Річарда Джонса на цю темуале він залишає вас здивованим і стурбованим своїм найближчим терміном. Що ти робиш?

Просто замінивши свою Dictionaryслабку хеш-таблицю, ви можете піггікувати існуючий GC і автоматично автоматично збирати недоступні для вас підграграфи! Назад до розгортання рекламних оголошень Ferrari.

Застосування 3: Оздоблення дерев

Ви звисаєте зі стелі цикліндричної кімнати біля клавіатури. У вас є 60 секунд, щоб просіяти деякі великі дані, перш ніж хтось знайде вас. Ви підготувались із прекрасним аналізатором на основі потоку, який покладається на GC для збору фрагментів AST після їх аналізу. Але ви розумієте, що вам потрібні додаткові метадані на кожному AST Nodeі вам це потрібно швидко. Що ти робиш?

Ви можете використовувати a Dictionary<Node, Metadata>для пов'язування метаданих з кожним вузлом, але, якщо ви не очистите його, чіткі посилання зі словника до старих вузлів AST дозволять зберегти їх живими та просочувати пам’ять. Рішення - це слабка хеш-таблиця, яка зберігає лише слабкі посилання на ключі, а сміття збирає прив’язки ключів і значень, коли ключ стає недоступним. Потім, оскільки вузли AST стають недоступними, вони збираються сміттям, а їх прив'язка до ключових значень видаляється зі словника, залишаючи відповідні метадані недоступними, щоб вони також збиралися. Тоді все, що вам потрібно зробити, після того, як ваш основний контур закінчиться, - просунути назад через вентиляційний отвір, пам’ятаючи замінити його так само, як заходить охоронець.

Зауважте, що у всіх трьох цих реальних додатках, які насправді траплялися зі мною, я хотів, щоб GC збирав якомога агресивніше. Ось чому це законні програми. Усі інші помиляються.


2
Слабкі посилання не працюватимуть для програми 2, якщо недоступні підграграфи містять цикли. Це тому, що в слабкій хеш-таблиці зазвичай є слабкі посилання на ключі, але чіткі посилання на значення. Вам знадобиться хеш-таблиця, яка підтримує чіткі посилання на значення лише тоді, коли ключ ще доступний -> дивіться ефемери ( ConditionalWeakTableу .NET).
Даніель

@Daniel Чи не повинен GC вміти працювати з недосяжними циклами? Як би це НЕ буде зібрано , коли недосяжний цикл сильних посилань буде збиратися?
бінкі

О, я думаю, що бачу. Я просто припустив, що ConditionalWeakTableсаме це використовуватимуть додатки 2 і 3, тоді як деякі люди на інших посадах насправді використовують Dictionary<WeakReference, T>. Не маю поняття, чому - ви завжди отримаєте тону нульових WeakReferenceзначень зі значеннями, які не можуть отримати доступ жоден ключ незалежно від того, як це зробити. Рідік.
бінкі

@binki: "Хіба GC не повинен вміти обробляти недоступні цикли? Як би це не було зібрано, коли буде зібраний недоступний цикл чітких посилань?". У вас є словник, введений на унікальні об'єкти, які неможливо відтворити. Коли один із ваших ключових об'єктів стає недоступним, він може бути зібраний сміттям, але відповідне значення у словнику навіть не буде вважатися теоретично недосяжним, оскільки звичайний словник буде сильно посилатися на нього, зберігаючи його живим. Отже, ви використовуєте слабкий словник.
Джон Харроп

@Daniel: "Слабі посилання не працюватимуть для додатка 2, якщо недоступні підграграфи містять цикли. Це тому, що в слабкій хеш-таблиці зазвичай є слабкі посилання на ключі, але чіткі посилання на значення. Вам потрібна хеш-таблиця, яка підтримує чіткі посилання на значення лише тоді, коли ключ ще доступний ". Так. Вам, мабуть, краще кодувати графік безпосередньо ребрами в якості покажчиків, щоб GC сам збирав його.
Джон Харроп

19

Враховуючи ці характеристики, я не можу придумати ситуацію, коли слабкі посилання були б корисні, можливо, хтось міг би мене просвітити?

Документ Майкрософт - слабкі шаблони подій .

У програмах можливо, що обробники, приєднані до джерел подій, не будуть знищені в координації з об'єктом слухача, який приєднав обробник до джерела. Така ситуація може призвести до витоку пам'яті. Фонд презентацій Windows (WPF) представляє модель дизайну, яку можна використовувати для вирішення цієї проблеми, надаючи виділений клас менеджера для певних подій та впроваджуючи інтерфейс для слухачів для цієї події. Ця модель дизайну відома як слабка модель подій.

...

Слабка модель подій призначена для вирішення цієї проблеми з витоком пам'яті. Слабка модель подій може використовуватися, коли слухачеві потрібно зареєструватися для події, але слухач не знає, коли потрібно зареєструватися. Модель слабкої події також може використовуватися, коли тривалість життя джерела джерела перевищує термін експлуатації слухача. (У цьому випадку корисність визначається вами.) Слабка модель подій дозволяє слухачеві зареєструватися та приймати подію, не впливаючи жодним чином на характеристики об'єкта життя слухача. Фактично мається на увазі джерело від джерела не визначає, чи слухач придатний до вивезення сміття. Посилання є слабкою посиланням, таким чином, називання шаблону слабких подій та відповідних API. Слухач може збирати сміття або іншим чином знищувати його, а джерело може продовжуватись, не зберігаючи посилання на невідбірні обробники на тепер знищений об'єкт.


Ця URL-адреса автоматично вибирає нову версію .NET (наразі 4,5), у якій "ця тема більше не доступна". Вибір .NET 4.0 замість цього працює ( msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx )
maxp

13

Дозвольте мені викласти це першим і повернутися до нього:

WeakReference корисний, коли ви хочете вести вкладки на об’єкті, але ви НЕ хочете, щоб ваші спостереження запобігали збиранню цього об'єкта

Тож почнемо з початку:

- Заздалегідь вибачте за будь-яке ненавмисне правопорушення, але я на хвилину повернусь до рівня "Діка і Джейн", оскільки ніколи не можна сказати цій аудиторії.

Отже, коли у вас є об'єкт X- давайте вказати його як екземпляр class Foo- він НЕ МОЖЕ жити самостійно (здебільшого вірно); Так само, як "Жодна людина не острів", існує лише декілька способів, які об'єкт може просунути до Islandhood - хоча це називається коренем GC в CLR говорять. Бути коренем 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, звичайно це не так неправда)

  • Ви хочете щось "транслювати" колекції предметів, але ви не хочете, щоб ця річ підтримувала їх живими; List<WeakReference>може підтримуватися легко, і навіть обрізають, видаляючи , деref.Target == null


1
Що стосується вашого другого випадку використання, то сміттєзбиральний апарат справляється з циркулярними посиланнями. "об'єкт A посилається на об'єкт B, який посилається на об'єкт A", безумовно, не є витоком пам'яті.
Джо Дейлі

@JoeDaley Я згоден .NET GC використовує алгоритм позначення та розгортання, який (я вважаю, що це правильно пригадую) позначає всі об'єкти для колекції, а потім слідує за посиланнями з "коріння" (посилання на об'єкти в стеку, статичні об'єкти), відміняючи об'єкти для колекції . Якщо існує кругова довідка, але жоден з об'єктів не є доступним у корені, об'єкти не позначені для колекції і, таким чином, мають право на збір.
ta.speot.is

1
@JoeDaley - Ви обоє, звичайно, правильні - поспішали там до кінця ... Я це відредагую.
JerKimball

4

введіть тут опис зображення

Як і логічні витоки, які дуже важко відстежити, але користувачі просто помічають, що запуск вашого програмного забезпечення довгий час, як правило, займає все більше і більше пам’яті та стає повільнішим і повільнішим, поки вони не перезапустяться? Я не.

Поміркуйте, що станеться, якщо, коли користувач запитує на видалення ресурсу програми вище, Thing2не вдасться належним чином обробити таку подію під:

  1. Покажчики
  2. Сильні посилання
  3. Слабка література

... і при якій одна з таких помилок, ймовірно, буде спіймана під час тестування, а яка не буде і летить під радаром, як помилка винищувача. Спільне право власності - це частіше, ніж більшість, безглузда ідея.


1

Дуже наочним прикладом слабких посилань, що використовуються для хорошого ефекту, є ConditionalWeakTable , який використовується DLR (серед інших місць) для приєднання додаткових "членів" до об'єктів.

Ви не хочете, щоб стіл підтримував живий об’єкт. Ця концепція просто не могла працювати без слабких посилань.

Але мені здається, що всі способи використання слабких посилань з'явилися довго після того, як вони були додані до мови, оскільки слабкі посилання були частиною .NET з версії 1.1. Це просто схоже на щось, що ви хочете додати, так що відсутність детермінованого знищення не поверне вас у кут, що стосується мовних особливостей.


Я фактично виявив, що хоча таблиця використовує концепцію слабких посилань, реальна реалізація не передбачає WeakReferenceтип, оскільки ситуація набагато складніша. Він використовує різні функціональні можливості, піддані CLR.
GregRos

-2

Якщо у вас кешований шар реалізований за допомогою C #, то набагато краще також помістити свої дані в кеш як слабкі посилання, це може допомогти покращити продуктивність шару кешу.

Вважайте, що цей підхід також може бути застосований до впровадження сеансу. Оскільки сесія є довгим живим об'єктом більшість часу, це може бути певний випадок, коли у вас немає пам’яті для нового користувача. У такому випадку буде набагато краще видалити ще якийсь об’єкт сеансу користувача, а потім викинути OutOfMemoryException.

Крім того, якщо у вашому додатку є великий об'єкт (велика таблиця пошуку тощо), його слід використовувати досить рідко, а відтворення такого об'єкта - не дуже дорога процедура. Тоді краще запропонуйте це як посилання на тиждень, щоб мати можливість звільнити пам’ять, коли вам це справді потрібно.


5
але проблема із слабкими посиланнями (див. відповідь, на яку я посилався) полягає в тому, що вони дуже охоче зібрані, а колекція не пов'язана з наявністю місця в пам'яті. Таким чином, у вас з’являється більше пропусків кешу, коли тиск на пам'ять не відбувається.

1
Але для вашого другого пункту щодо великих об'єктів, MSDN doc заявляє, що хоча довгі слабкі посилання дозволяють вам відтворити об'єкт, його стан залишається непередбачуваним. Якщо ви збираєтесь відтворювати його з нуля кожен раз, навіщо турбуватися з використанням слабкої посилання, коли ви можете просто викликати функцію / метод, щоб створити її на вимогу і повернути тимчасовий екземпляр?

Існує одна ситуація, коли кешування корисно: якщо часто створювати незмінні об’єкти, багато з яких будуть ідентичними (наприклад, читання багатьох рядків з файлу, який, як очікується, має багато дублікатів), кожен рядок буде створений як новий об'єкт , але якщо рядок відповідає іншому рядку, на який вже існує посилання, ефективність пам’яті може бути підвищена, якщо новий екземпляр буде відмовлено і замінено посилання на попередній екземпляр. Зауважте, що ця заміна корисна, оскільки інша посилання все-таки проводиться. Якщо це не був код, він повинен зберігати новий.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.