Как я могу решить необъяснимые исключения ObjectDisposedException в моем приложении C # / MVVM? - PullRequest
2 голосов
/ 19 октября 2011

Я написал свое первое приложение MVVM.Когда я закрываю приложение, я часто получаю причину сбоя, вызванную ObjectDisposedException.Сбой появляется, когда приложение умирает, сразу после того, как окно приложения исчезает.

Получить трассировку стека было трудно ( см. Мой другой вопрос ), но, наконец, я это сделал и обнаружил, что моя трассировка стекаполностью содержится в библиотеках C # (kernel32! BaseThreadStart, mscorwks! Thread, mscorwks! WKS и т. д.).

Кроме того, этот сбой противоречив.После моей последней проверки и восстановления это перестало происходить ... на некоторое время.Тогда это вернулось.Как только это начинает происходить, оно продолжает происходить, , даже если я "Очистить" и восстановить Но стирание и проверка иногда заставляет его остановиться на некоторое время.

Что я думаю, что происходит:

Я думаю, что GarbageCollector делает что-то смешное, избавляясь от моегоViewModels.У моего деструктора класса ViewModelBase есть WriteLine для регистрации при вызове деструктора, и из моих 4 ViewModels удаляются только 2 или 3, и, кажется, это зависит от извлечения (например, когда я запускаю его на моем, я вижу последовательно повторяющиесяпоследовательность, но мой коллега видит другую последовательность с расположенными разными объектами).

Так как в трассировке стека нет ни одного из вызовов моего кода, я думаю, это означает, что это не мой код, который вызываетметод удаленного объекта.Так что я думаю, что CLR тупой.

Имеет ли это смысл?Есть ли какой-нибудь способ, которым я могу заставить GC быть последовательным?Это красная сельдь?

Другие детали, которые могут помочь: Все мои Views и ViewModels создаются в обработчике события запуска приложения в моем файле App.xaml.cs.Тот же обработчик назначает ViewModels для DataContexts.Я не уверен, является ли это правильной практикой MVVM (как я уже говорил, мое первое приложение MVVM), но я не понимаю, почему это может вызвать плохое поведение.

Я могу вставить код, если необходимо.

Ответы [ 3 ]

13 голосов
/ 19 октября 2011

Мой деструктор класса ViewModelBase имеет WriteLine для записи при вызове деструктора,

Это действительно плохо.Я надеюсь, что вы включили это только в отладочной сборке.

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

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

То, что вы видите, что вещи происходят в разных порядках в разное время,вполне ожидаемо, как мы увидим ниже.

Правильное написание деструктора - одна из самых сложных вещей в C #;то, что вы получаете исключение во время последнего раунда финализации перед тем, как процесс завершится, указывает на то, что вы, вероятно, делаете это неправильно.

Так что я не думаю, что CLR глупа.

Обвинение инструмента в вашей ошибке вряд ли поможет вам решить вашу проблему.

Вот что должен знать каждый перед написанием любого деструктора:

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

  • Деструкторы запускаются, даже если объект никогда не был инициализирован,Вполне возможно, что после того, как объект выделен, а код находится в середине конструктора, возникает исключение.Объект выделен, вы не подавили финализацию, и поэтому он должен быть уничтожен. Деструктор должен быть устойчивым к лицом не полностью инициализированного объекта.

  • Если объект находится под замком, предназначенным для обеспечения последовательной мутации, игенерируется исключение, и блок finally не восстанавливает согласованное состояние, тогда объект будет в несогласованном состоянии после завершения. Разрушители должны быть устойчивыми к объектам с несовместимым внутренним состоянием в результате прерванных транзакций.

  • Разрушители могут работать в в любом порядке .Если у вас есть дерево объектов, которые все ссылаются друг на друга, и все они мертвы одновременно, деструкторы для каждого объекта могут быть запущены в любое время. Деструкторы должны быть устойчивыми по отношению к объектам, внутреннее состояние которых относится к другим объектам, которые были или не были просто разрушены.

  • Объекты, ожидающие уничтожения в очереди финализатора живы согласно сборщику мусора. Деструктор заставляет ранее мертвый объект временно (мы надеемся!) Снова стать живым. Если логика вашей программы зависит от мертвых объектов, оставшихся мертвыми, вы должны быть очень осторожны со своими деструкторами.(И если логика деструктора снова делает объект постоянно живым, у вас могут быть большие проблемы. Не делайте этого.)

  • Поскольку объекты, ожидающие уничтожения, живы , и они определены как нуждающиеся в уничтожении, поскольку GC классифицировал их как мертвых , , объект, ожидающий завершения, автоматически перемещается до одного поколения в сборщике мусора поколений . Это означает, что восстановление хранилища сборщиком мусора не может произойти, пока объект не будет мертв в течение секунд времени. Поскольку объект только что перешел к более позднему поколению, это может не быть определено в течение длительного времени. Деструкторы приводят к тому, что распределение недолгой памяти становится намного более долгим, что может серьезно повлиять на производительность сборщика мусора в некоторых сценариях. Тщательно продумайте, прежде чем писать деструктор для большого недолговечного объекта (или, что еще хуже, маленький недолговечный объект, из которого вы собираетесь сделать миллионы); объекты с деструкторами не могут быть освобождены сборщиком нулей, если вы явно не подавите финализацию .

  • Разрушители не гарантированы для вызова. Сборщик мусора не требуется для запуска деструкторов объекта до завершения процесса, даже если они известны быть мертвым Ваша логика не может зависеть от ее правильности от вызываемых деструкторов. Многое может помешать вызову деструкторов - например, FailFast, исключение переполнения стека или кто-то выдернет шнур питания из стены. Программы должны быть устойчивыми перед лицом деструкторов, никогда не вызываемых.

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

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

4 голосов
/ 19 октября 2011

Ваше приложение выдает исключение, потому что ваше действие записи в журнал при уничтожении ViewModel не завершилось при выходе из основного приложения.

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

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

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

2 голосов
/ 19 октября 2011

Я думаю, что GarbageCollector делает что-то смешное при утилизации моих ViewModels.У моего деструктора класса ViewModelBase есть WriteLine, чтобы регистрировать, когда деструктор вызывается

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

Вы должны понимать, что финализаторы не работают в предсказуемом порядке.GC может вызывать финализаторы, когда и в нужном им порядке, что, вероятно, объясняет, почему вы получаете на первый взгляд случайное поведение исключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...