Visual Basic, .NET, ASP, VBScript
 

   
   
     

Форум - Общий форум

Страница: 1 | 2 | 3 |

 

  Вопрос: GC финализирует объект без причины Добавлено: 01.09.12 20:36  

Автор вопроса:  Programmer
Есть объект, который наследуется от MemoryStream. Внутри него создается BinaryWriter, который использует этот самый объект. Ссылка на BinaryWriter используется в коде. Т.е. наследник MemoryStream доступен из Call stack -> локальная переменная типа BinaryWriter -> свойство BaseStream.

Во время очередного обращения к BinaryWriter.Write я узнаю, что мой объект был финализирован (в деструкторе устанавливаю флаг). Как такое вообще возможно?

Такое происходит при каждом запуске программы в течении 5-10 минут после запуска.

Ответить

  Ответы Всего ответов: 45  

Номер ответа: 1
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #1 Добавлено: 01.09.12 21:50
Код в студию

Ответить

Номер ответа: 2
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #2 Добавлено: 01.09.12 22:07
Я так полагаю, в коде присутствует что-то вроде

using (BinaryWriter writer = new BinaryWriter(innerStream))
{
     ....
}

После выхода из блока using у BinaryWriter вызывается метод Dispose, который, в свою очередь, вызывает Dispose для внутреннего потока. Это стандартное поведение (то же самое касается и других классов FCL, который работают как надстройка над другим потоком). Если в твоем случае это поведение не требуется, то просто убери блок using. При финализации BinaryWriter сборщиком мусора Dispose потока не будет вызываться.

Также учти, что если используешь какие-то Reader'ы (типа StreamReader), то корректную работу не получится обеспечить, так как Reader будет выбирать в свой внутренний кеш определенную порцию данных из потока. И возможна ситуация, когда данные считаны в буфер reader'а, уже пропали из потока, но не были считаны через методы *reader.Read..., и фактически будут утеряны.

Ответить

Номер ответа: 3
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #3 Добавлено: 01.09.12 22:18
Эту ошибку не удастся воспроизвести только на основе кода класса, который я описал. Такое поведение возникает только при многопоточной работе, правильном положением звезд на небе и соблюдении прочих условий энвайронмента.

Возможно, существует хоть какое-то объяснение самого факта того, что переменная, которая используется в выполняемом в данный момент коде, вдруг финализируется? При этом объект доступен по ссылке, а флаг говорит, что вызван деструктор.

Ответить

Номер ответа: 4
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #4 Добавлено: 01.09.12 22:20
Разве using не вызывает only Dispose? Я говоре именно о вызове деструктора.

Ответить

Номер ответа: 5
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #5 Добавлено: 01.09.12 22:23
Сложно вам что-то сказать, учитывая что в дотнете нет понятия деструкторов.
Повторюсь, нужен код

Ответить

Номер ответа: 6
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #6 Добавлено: 01.09.12 22:25
  1. После выхода из блока using у BinaryWriter вызывается метод Dispose, который, в свою очередь, вызывает Dispose для внутреннего потока. Это стандартное поведение (то же самое касается и других классов FCL, который работают как надстройка над другим потоком). Если в твоем случае это поведение не требуется, то просто убери блок using. При финализации BinaryWriter сборщиком мусора Dispose потока не будет вызываться.

Мой объект IDisposable, в Dispose base не вызывается. Так же перегружен Dispose, вызова base.Dispose нет. Более того, логи говорят, что деструктор вызывается до Dispose, т.е. Dispose не вызывается вообще.

И нет, в обсуждаемом участке кода using не присутствует.

Ответить

Номер ответа: 7
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #7 Добавлено: 01.09.12 22:34
Programmer пишет:
Эту ошибку не удастся воспроизвести только на основе кода класса, который я описал. Такое поведение возникает только при многопоточной работе, правильном положением звезд на небе и соблюдении прочих условий энвайронмента.

То есть вы, имея на руках код, не можете найти причину проблемы, приходите на форум и хотите чтоб проблему нашли даже не видя код?

Ответить

Номер ответа: 8
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #8 Добавлено: 01.09.12 22:37
Если все так как вы говорите, и в вашем коде 100% нет ошибки, то поздравляю вас, вы нашли баг в .NET Framework. Идите на connect.microsoft.com и заводите багрепорт.
Правда вынужден вас огорчить, код, воспроизводящий проблему, туда все равно прийдется выложить. Плюс, с 95% вероятностью могу сказать, что редкий многопоточный баг с вероятностью 95% закроют как unable to reproduce (как закрыли мой, который, в отличие от вашего, 100% существует)

Ответить

Номер ответа: 9
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #9 Добавлено: 01.09.12 22:45
Не буду спорить - может я где-то накосячил. Вот сейчас я специально проверил - воспроизвести ошибку не удается. Мое приложение создает 300 потоков, каждый из которых в бесконечном цикле создает стрим в локальной переменной и записывает в него. Удается воспроизвести только внутри "полезного" приложения, но оно слишком большое, чтобы выкладывать весь код.

Ответить

Номер ответа: 10
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #10 Добавлено: 01.09.12 22:49
То есть вы, имея на руках код, не можете найти причину проблемы, приходите на форум и хотите чтоб проблему нашли даже не видя код?
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(), когда он "вот прямо сейчас" используется...

Если рассматривать этот вопрос абстрактно от конкретно моего кода, из-за чего такое может происходить?

Ответить

Номер ответа: 11
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #11 Добавлено: 01.09.12 22:57
Programmer пишет:
Мое приложение создает 300 потоков

facepalm

Programmer пишет:
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(), когда он "вот прямо сейчас" используется...

Если рассматривать этот вопрос абстрактно от конкретно моего кода, из-за чего такое может происходить?

Это не может происходить, так как если бы это происходило, то за 10 лет существования дотнета это поведение бы обнаружили и исправили проблему.

Или же вы нашли уникальный баг, который до этого никто не мог воспроизвести, тогда см выше.

Programmer пишет:
Удается воспроизвести только внутри "полезного" приложения, но оно слишком большое, чтобы выкладывать весь код.

Выложите проект где-нибудь вне форума.

Ответить

Номер ответа: 12
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #12 Добавлено: 01.09.12 23:08
facepalm
Нужно было уточнить, что внутри присутствует Thread.Sleep(Random(...))?

Выложите проект где-нибудь вне форума.

Это коммерческое приложение, на такое я пойти не могу. Да, с этого "надо было начинать", но я бы выложил участок кода, если бы удавалось воспроизвести ошибку. Но не целый проект (а учитывая references - проекты).

Это не может происходить, так как если бы это происходило, то за 10 лет существования дотнета это поведение бы обнаружили и исправили проблему.
Бывает и такое. Значит, ничего не поделаешь. Поставлю одним блоком try-catch больше и постараюсь не вспоминать :(

Ответить

Номер ответа: 13
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #13 Добавлено: 01.09.12 23:43
Programmer пишет:
Нужно было уточнить, что внутри присутствует Thread.Sleep(Random(...))?

Не нужно, это и так подразумевается. Даже если бы не было Thread.Sleep, ваши 300 потоков все равно большую часть времени занимались бы ожиданием в очереди (если, конечно, у вас нет 300-ядерного процессора)

Даже не говоря о том, что максимальную производительность можно получить тогда когда кол-во не больше кол-ва ядер ядер (при условии, что они все это время работают), стоит отметить что на каждый поток в оперативной памяти выделяется чуть более чем 1 мб под стек и контекст, в результате у вас отъедается 300+ мб оперативной памяти под потоки, поторые большую часть времени просто спят.

Programmer пишет:
Это коммерческое приложение, на такое я пойти не могу. Да, с этого "надо было начинать", но я бы выложил участок кода, если бы удавалось воспроизвести ошибку. Но не целый проект (а учитывая references - проекты).

Никто не собирается воспроизводить вашу ошибку. Выложите код класса в котором будет проблема, если вы там налажали это будет видно даже без его запуска.

Ответить

Номер ответа: 14
Автор ответа:
 EROS



Вопросов: 58
Ответов: 4255
 Профиль | | #14 Добавлено: 02.09.12 00:02
Programmer пишет:
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(),

Только по одной причине: Объект никем не используется и на него НЕТ ссылок. Либо кто-то явно вызвал Dispose для объекта

Чудес на свете не бывает.. (с)

Ответить

Номер ответа: 15
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #15 Добавлено: 02.09.12 00:04
Если вас интерисовал финализатор...
Финализатор может быть вызван после того как объект поставлен на очередь финализации, когда до него, собственно, дошла очередь (финализация объектов выполняется в отдельном потоке).
Постановка в очередь финализации происходит во время сборки мусора в том случае, если объект признан мусором, то есть на него нет ни одной живой ссылки (при этом допускается наличие слабых ссылок WeakReference, но если вы или ваши компоненты явно не используют этот класс, то этим можно пренебречь).

Возможна ли ситуация при которой вы обращаетесь к объекту и выясняется что он уже финализирован?
Да, возможна.
1) Так называемое "оживление".
Если объект имеет финализатор, то, как говорилось, после сборки мусора он помещается в очередь на финализацию, и НЕ УДАЛЯЕТСЯ из кучи. То есть сборка мусора завершилась, а объект остается в памяти, так как на него есть ссылка из очереди финализации. После того, как финализатор запустился, ссылка на объект удаляется из очереди на финализацию (при этом делается пометка, что данный объект уже был финализирован) и при следующей сборке мусора память, которую он занимал, будет освобождена физически (при дефрагментации кучи его место займут живые объекты).
Если же финализатор добавит ссылку на свой объект в какой-то корень (например, в какое-то статическое поле), то при следующей сборке мусора выяснится что объект уже не мусор, и он останется в памяти. И к нему можно будет обращаться по той ссылке, которую он создал. И фактически будет выполняться обращение к объекту, у которого уже срабатывал финализатор.
При этом, когда объект в очередной раз будет признан мусором, его финализатор уже не будет выполняться, так как на нем все еще стоит метка, свидетельствующая о том что он прошел процедуру финализации. Эту метку можно сбросить, вызвав GC.ReRegisterForFinalize. Тогда в следующий раз, когда объект будет признан мусором, для него опять будет выполнена финализация. И так, собственно, может происходить до бесконечности.

2. Ссылки между "мусорными" объектами.
Предположим, объект А ссылается на объект Б. Больше никто не ссылается ни на объект А, ни на объект Б. При сборке мусора оба объекта будут признаны мусором, даже не смотря на то, что на объект Б есть 1 ссылка. Причина, по которой объект признан мусором состоит в том, что ни до одного из этих объектов нельзя добраться из корней, с которых начинает работу сборщик мусора.
Допустим, у обоих объектов есть финализатор. Они оба ставятся в очередь финализации, и для обоих после этого запускается финализатор. Сначала финализатор выполняется для объекта Б, он проходит эту процедуру. Затем финализатор запускается для объекта А. В финализаторе он пытается обратиться по своей ссылке к объекту А. Произошло обращение к объекту, который прошел процедуру финализации.

Это происходит потмоу, что CLR не гарантирует какой-либо порядок запуска финализаторов объектов. То есть они запускаются в неопределенном порядке, и в финализаторе нельзя полагаться на то, что они запускаются в определенном порядке.
Кроме того CLR не гарантирует, что финализатор объекта вообще будет вызван, т.е. нельзя полагаться на то что финализатор вообще сработает, даже если сборщик мусора работает штатно.

По большму счету есть очень мало ситуаций, в которых приходится в класс добавлять финализатор (за последние 2 года, наверное, я не написал ни одного, даже забыл их синтаксис). Могу предположить, что в вашем случае их реализация связана с непониманием того как работает менеджер памяти в .NET

Ответить

Страница: 1 | 2 | 3 |

Поиск по форуму



© Copyright 2002-2011 VBNet.RU | Пишите нам