Здравствуйте, FlyDN, Вы писали:
FDN>В следующем коде "Finalizer" мы не увидим никогда, а почему?
Ну например я вижу (FW 2.0). Только после нажатия enter.
Здравствуйте, FlyDN, Вы писали:
FDN>В следующем коде "Finalizer" мы не увидим никогда, а почему?
Ответ на данный вопрос скрыт внути класса Timer. Если, скажем, сделать ему Dispose, то наблюдать вызов финалайзера мы начинаем в ождидаемом месте.
А дело все в том, что все созданные таймеры регистрируются в VM. Код из Рефлектора:
public Timer(TimerCallback callback)
{
int num1 = -1;
int num2 = -1;
StackCrawlMark mark1 = StackCrawlMark.LookForMyCaller;
this.TimerSetup(callback, this, (uint) num1, (uint) num2, ref mark1);
}
Здравствуйте, Mab, Вы писали:
Mab>Итак, глобальная таблица косвенно держит делегат, который держит экземпляр Test.
Mab>Мораль: не пренебрегайте вызовом Dispose. Иначе некоторе финалайзеры у вас вызовутся лишь при app domain shutdown
Где то так я и думал.
MSDN говорит.
As long as you are using a Timer, you must keep a reference to it. As with any managed object, a Timer is subject to garbage collection when there are no references to it. The fact that a Timer is still active does not prevent it from being collected.
И если поправить код:
public Test()
{
// _timer = new Timer(new TimerCallback(TimerCallbackFunc), null, 0, 0);
Timer timer = new Timer(new TimerCallback(TimerCallbackFunc), null, 0, 0);
}
То после сборки мусора timer по идее должен собраться GC, но и после этого обьект продолжает жыть. То есть Timer в финализаторе не удаляет ссылку.Не хорошо.
Действительно, нужно звать Dispose всегда, иначе получим MemoryLeak на ровном месте.
Здравствуйте, FlyDN, Вы писали:
FDN>MSDN говорит.
Увы, но действительно говорит он неправду И приведенный пример подтвреждает это.
FDN>То есть Timer в финализаторе не удаляет ссылку.
Да нет, Timer здесь не виноват. Это проблема дизайна. Зарегистрированный таймер получает ссылку от GC root-а. Соответственно, пока не будет Dispose он будет жить.
Для доказательства запустил пример под MemoryProfiler. Он показывает такую цепочку ссылок до Test:
Здравствуйте, Mab, Вы писали:
Mab>Здравствуйте, FlyDN, Вы писали:
FDN>>MSDN говорит. Mab>Увы, но действительно говорит он неправду И приведенный пример подтвреждает это.
FDN>>То есть Timer в финализаторе не удаляет ссылку. Mab>Да нет, Timer здесь не виноват. Это проблема дизайна. Зарегистрированный таймер получает ссылку от GC root-а. Соответственно, пока не будет Dispose он будет жить.
Странно рефлектором посмотрел код Timer.Dispose()
public void Dispose()
{
this.timerBase.Dispose();
}
Здравствуйте, FlyDN, Вы писали:
FDN>Отличаются они только выделенным. Получается при вызове Dispose() ссылка на обьект обнуляется а после вызова Finalize() нет
Фишка в том, что вызов Finalize не может произойти в принципе, поскольку на таймер остается ссылка от GC root.
Здравствуйте, Mab, Вы писали:
FDN>>Отличаются они только выделенным. Получается при вызове Dispose() ссылка на обьект обнуляется а после вызова Finalize() нет Mab>Фишка в том, что вызов Finalize не может произойти в принципе, поскольку на таймер остается ссылка от GC root.
Здравствуйте, FlyDN, Вы писали:
FDN>А смысл тогда в TimerBase обьявлять Finalize?
Вы меня спрашиваете? Возможно автор данного кода попросту забыл о том, что ссылка остается. И сделал copy-paste для финалайзера.
Здравствуйте, Mab, Вы писали:
Mab>Здравствуйте, FlyDN, Вы писали:
FDN>>А смысл тогда в TimerBase обьявлять Finalize? Mab>Вы меня спрашиваете?
Нет, парней из MS, зная что они этого никогда не увидят
Да, тему я наверное не правильно назвал, GC тут совсем не причем.
Здравствуйте, Mab, Вы писали:
Mab>Здравствуйте, FlyDN, Вы писали:
FDN>>В следующем коде "Finalizer" мы не увидим никогда, а почему? Mab>Ответ на данный вопрос скрыт внути класса Timer. Если, скажем, сделать ему Dispose, то наблюдать вызов финалайзера мы начинаем в ождидаемом месте.
Mab>А дело все в том, что все созданные таймеры регистрируются в VM. Код из Рефлектора:
Что то ты перемудрил. Ничего нигде не регистрируется. Просто когда в мы создаем Таймер (или любой неуправляемый объект, в процессе инициализации которого используется callback-функция) мы не можем напрямую передавать объект делегата. Для этого передается его указатель.Но при такой передаче есть большая вероятность, что делегат может быть собран GC. Тогда при вызове из native кода мы можем получить неопределенный результат.
Не понимаю поведение GC
От:
Аноним
Дата:
08.11.05 11:40
Оценка:
Вы случайно не в дебаге это компилировали?
Если да, то в чем вопрос?
А>Ничего нигде не регистрируется.
Это утверждение не верно. Регистрация происходит в глобальной таблице хэндлов на объекты.
А>Просто когда в мы создаем Таймер (или любой неуправляемый объект, в процессе инициализации которого используется callback-функция) мы не можем напрямую передавать объект делегата. Для этого передается его указатель.
Не надо путать вызовы через pinvoke и вызовы методов через MethodImplOptions.InternalCall. В первом случае обязательно создание unmanaged thunks (хотя бы через Marshal.GetFunctionPointerForDelegate). Во второй случае вызов уходит непосредственно в потроха виртуальной машины. Поддержание целостности ссылок при таком вызове -- ее проблема, которую она успешно решеает.
А>Но при такой передаче есть большая вероятность, что делегат может быть собран GC. Тогда при вызове из native кода мы можем получить неопределенный результат.
Не очень понятно, к чему это написано. Если имелось в виду, что иногда такие вызовы приведут к каким-то недопустимым действиям, то это неправда.
Для доказаетельства мне придется продемонстрировать код виртуальной машины из Ротора. Раз на слово мне верить не хотят
Отмечу, что в метод передатеся самый что ни на есть настоящий указатели на объекты. Удержанием этих указателей от сбора занимается макрос GCPROTECT_BEGIN(gc):
//------------------------------------------------------------------------
// These macros GC-protect OBJECTREF pointers on the EE's behalf.
// In between these macros, the GC can move but not discard the protected
// objects. If the GC moves an object, it will update the guarded OBJECTREF's.
// Typical usage:
//
// OBJECTREF or = <some valid objectref>;
// GCPROTECT_BEGIN(or);
//
// ...<do work that can trigger GC>...
//
// GCPROTECT_END();
//
//
// These macros can also protect multiple OBJECTREF's if they're packaged
// into a structure:
//
// struct xx {
// OBJECTREF o1;
// OBJECTREF o2;
// } gc;
//
// GCPROTECT_BEGIN(gc);
// ....
// GCPROTECT_END();
Идем далее. Происходит вызов DelegateInfo::MakeDelegateInfo. Вот что он делает:
Здравствуйте, Mab, Вы писали:
А>>Ничего нигде не регистрируется. Mab>Это утверждение не верно. Регистрация происходит в глобальной таблице хэндлов на объекты.
Если бы ты знал, сколько у VM таких таблиц, ты бы не разбрасывался словом "глобальный" так легко
А>>Просто когда в мы создаем Таймер (или любой неуправляемый объект, в процессе инициализации которого используется callback-функция) мы не можем напрямую передавать объект делегата. Для этого передается его указатель.
Mab>Не надо путать вызовы через pinvoke и вызовы методов через MethodImplOptions.InternalCall.
Не надо, вот поэтому я их и не путаю . Кстати, встречный вопрос. А в данном случае между ними какая разница? Могу дать сразу ответ, но это не интересно.
Mab>В первом случае обязательно создание unmanaged thunks (хотя бы через Marshal.GetFunctionPointerForDelegate).
GetFunctionPointerForDelegate — это игра с GCHandle. Ничего больше.
Mab>Во второй случае вызов уходит непосредственно в потроха виртуальной машины. Поддержание целостности ссылок при таком вызове -- ее проблема, которую она успешно решеает.
Да ничего она не решает. Она делает тоже самое, что и управляемый код. Надеюсь не нужно обяснять, что VM — это unmanaged приложение, и для нее определена такая же политика, как для обычного приложения. GC наплевать, кто там держит ссылку.
А>>Но при такой передаче есть большая вероятность, что делегат может быть собран GC. Тогда при вызове из native кода мы можем получить неопределенный результат. Mab>Не очень понятно, к чему это написано. Если имелось в виду, что иногда такие вызовы приведут к каким-то недопустимым действиям, то это неправда.
Ага, а AccessViolationException — это у нас нормальное поведение программы
Mab>Для доказаетельства мне придется продемонстрировать код виртуальной машины из Ротора. Раз на слово мне верить не хотят
Mab>Отмечу, что в метод передатеся самый что ни на есть настоящий указатели на объекты. Удержанием этих указателей от сбора занимается макрос GCPROTECT_BEGIN(gc):
Верно. Теперь осталось понять, зачем все это нужно было. Ответ — ищи в моей предыдущем топике. Ты все слишком перемудрил, нужно мыслить проще.
Mab>Идем далее. Происходит вызов DelegateInfo::MakeDelegateInfo. Вот что он делает: Mab>
А>Если бы ты знал, сколько у VM таких таблиц, ты бы не разбрасывался словом "глобальный" так легко
А я что, имею честь беседовать с одним из авторов .NET VM?
Данная таблица глобальна в пределах домена.
А>А в данном случае между ними какая разница?
Хотя бы в отсутствии маршаллинга.
А>GetFunctionPointerForDelegate — это игра с GCHandle. Ничего больше.
Ну и к чему это замечание относится? CreateHandle, к которому приводит регистрация таймера, имеет сходный эффект.
А>Да ничего она не решает. Она делает тоже самое, что и управляемый код.
Смысл данного утверждения непонятен.
А>Надеюсь не нужно обяснять, что VM — это unmanaged приложение, и для нее определена такая же политика, как для обычного приложения.
В каком смысле "политика"?
А>GC наплевать, кто там держит ссылку.
GC не телепат. О том, что ссылку кто-то держит, он узнает лишь если информация о владении сслылкой явно оформлена. Для случая кода, порождаемого JIT-ом, такие таблицы строятся автоматически. А вот для случая unmanaged-кода виртуальной матшны их нет и приходится предпринимать специальные усилия (GCPROTECT).
А>Ага, а AccessViolationException — это у нас нормальное поведение программы
Я наверное тупой, но не понимаю смысл юмора. Увы.
А>Ты все слишком перемудрил, нужно мыслить проще.
Мыслить нужно проще. Но одновременно не нужно делать ложных утверждений. А именно, утверждение о том, что создание таймера не приводит к регистрации нового GC-рута, не верно. Или может я неправильно тебя понял?
А>Ты не поверишь, но здесь мы только создали делегат. Ничего больше
Ты не поверишь, но ты не прав.
По твоему, делегат -- это структура DelegateInfo, содержащая хэндлы типа m_delegateHandle или m_stateHandle? Ну-ну
DelegateInfo не имеет отношения к экзеплярам класса Delegate. Для того, чтобы понять, как устроены делегаты, советую изучить файлы delegate.cs,comdelegate.h/cpp.