Вопрос по c#, garbage-collection, events, lambda – Предотвращают ли лямбды, назначенные событию, сборку мусора для объекта-владельца?

7

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

{
    var o = new MyClass();
    o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
Это должно иметь право на GC. Я не уверен в этом достаточно, чтобы ответить на него. Tim S.
@sixlettervariables: я думаю, что это не так, поскольку мой вопрос спрашивает, поддерживает ли объект лямбда, а не наоборот. user816328
Я не понимаю, почему это ведет себя иначе, чем подписка любой другой функции (лямбда или иным образом) на событие. Так что я согласен с Тимом. Matt Razza
возможный дубликатGarbage collection when using anonymous delegates for event handling Joel Etherton
@JoelEtherton: я думаю, что это лучший дубликат,Can using lambdas as event handlers cause a memory leak? user7116

Ваш Ответ

4   ответа
0

что именно вы подразумеваете под «локальным контекстом». Для сравнения:

static void highMem()
{
    var r = new Random();
    var o = new MyClass();
    o.MyClassEvent += a => { };
}
static void Main(string[] args)
{
    highMem();
    GC.Collect(); //yes, it was collected
}

Для того, чтобы:

static void Main(string[] args)
{
    var r = new Random();
    {
        var o = new MyClass();
        o.MyClassEvent += a => { };
    }
    GC.Collect(); //no, it wasn't collected
}

В моей среде (отладочная сборка, VS 2010, Win7 x64) первый подходил для GC, а второй не был проверен (как было проверено, если MyClass занимал 200 МБ памяти и проверял ее в диспетчере задач), даже если он был вне области. Я полагаю, это потому, что компилятор объявляет все локальные переменные в начале метода, так что в CLR,o не выходит за рамки, даже если вы не можете использовать его в коде C #.

o выходит за рамки, но область имеет мало общего с сборкой мусора, живые ссылки. Если пространство стека ссылки используется, когда ссылка все еще находится в области видимости, ее можно собрать. В некоторых случаях он может быть даже собран до завершения операций с ним.
2

o будет освобожден. Не беспокойтесь об этом.

Чуть дольше отвечу:

Ваш код делает более или менее следующее:

Create some local storage on that thread for a reference to the new MyClass (o). Create the new MyClass Store a reference to the new MyClass at o. Create a new delegate from the lambda. Assign that delegate to the event (o now has a reference to the delegate).

В любой момент во время этого GC может остановить потоки, а затем проверить, какие объекты являются или не являются корневыми. Корни являются объектами, которые являются статическими или находятся в локальном хранилище потока (под которыми я подразумеваю локальные переменные в исполнении данного потока, реализуемые стеком, а не "Thread-Local Storage"), который по существу является формой статика). Корневые объекты - это корни, объекты, на которые они ссылаются, объекты, на которые они ссылаются, и так далее. Корневые объекты не будут собираться, остальные будут собираться (за исключением некоторых дополнительных вещей, связанных с финализаторами, которые мы пока проигнорируем).

Пока ни один момент после создания объекта MyClass не был укоренен локальным хранилищем потока.

До сих пор ни после того, как созданный объект делегата не был укоренен ни локальным хранилищем потока, ни MyClass, имеющим ссылку на него.

Теперь, что происходит дальше?

Это зависит. Лямбда не будет поддерживать MyClass в живых (MyClass поддерживает его, но когда MyClass работает, то и лямбда тоже). Этого недостаточно, чтобы ответить на "Will" o 'a'; иметь право на сборку мусора здесь? & quot; хоть.

void Meth0()
{
    var o = new MyClass();
    o.MyClassEvent += (args) => {};
}//o is likely eligible for collection, though it doesn't have to be.

void Meth1()
{
  int i = 0;
  {
    var o = new MyClass();
    o.MyClassEvent += (args) => {};
  }//o is likely not eligible for collection, though it could be.
  while(i > 100000000);//just wasting time
}

void Meth2()
{
  {
    var o = new MyClass();
    o.MyClassEvent += (args) => {};
    int i = 0;//o is likely eligible for collection, though it doesn't have to be.
    while(i > 100000000);//just wasting time
  }
}

void Meth3()
{
  {
    var o = new MyClass();
    o.MyClassEvent += (args) => {};
    var x0 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x1 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x2 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x3 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x4 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x5 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x6 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x7 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x8 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x9 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x10 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    var x11 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
    int i = 0;
    while(i > 100000000);//just wasting time
  }
}

Кажется, что Meth0 самый простой, но на самом деле это не так, поэтому мы вернемся к нему.

В Meth1 реализацияlikely оставить локальное хранилище как есть, так как оно больше не требуется, так что покаo находится вне области действия, реализация все еще будет использовать это локальное хранилище.

В Meth2 реализацияcould использовать то же локальное хранилище, которое использовалось дляo заi поэтому, даже если он все еще находится в области видимости, он будет иметь право собирать («в области видимости» означает, что программист может решить что-то сделать с этим, но он или она этого не сделали, и скомпилированный код не нуждается в таком концепция).

Meth3 с большей вероятностью будет повторно использовать хранилище, потому что его временное использование делает эту реализацию более разумной по сравнению с первоначальным выделением всего хранилища.

Ничего из этогоhave быть таким. В начале Meth2 и Meth3 могли выделить все хранилище, необходимое для метода. Meth1 может повторно использовать хранилище, потому что не имеет значения, чтобы переупорядочитьi а такжеo быть назначенным.

Meth0 более сложный, потому что он может зависеть от того, что вызывающий метод делает с локальным хранилищем, а не от того, когда происходит очистка (оба являются законными реализациями). IIRC всегда проводит очистку с текущей реализацией, но я не уверен, и в любом случае это не имеет значения.

В общем, область действия не важна, но может ли компилятор и позднее JITter использовать и использует ли локальное хранилище, связанное с объектом. Можно даже очистить объект до вызова его методов и свойств (если эти методы и свойства не используютthis или любое из полей объекта, потому что оно будет работать нормально, если объект был удален!).

5

o освободится, и лямбда-функция тоже будет. Нет ссылок наo откуда-то еще, поэтому нет никаких причин, по которым он не должен быть освобожден.

1

больше нет ссылок на объект, поэтому он собирает мусор.

Откуда ты знаешь, что в локальном хранилище больше нет ссылок на него?
Неважно, есть ли ссылки на что-то. Фактически, лямбда - это, вероятно, объект, выделенный из кучи, со ссылкой на этот объект, поэтому ваше утверждение неверно. Для ГК важно, является ли объектreachable.

Похожие вопросы