Pull to refresh

Comments 27

Круто, спасибо, что поделились опытом!

В первом примере не совсем понятно, почему wr становится доступным сборщику мусора сразу же после вызова calc.DoCalculations(). DoCalculations() вызывает unmanaged код, да еще и асинхронно? В таком случае, стоило бы об этом упомянуть.
Или, возможно, я чего-то недопонял :)
calc.DoCalculations() действительно вызывает неуправляемый код (а зачем ему еще указатель?), но все происходит синхронно. А wr попадает к сборщику мусора потому что грубо говоря больше нигде не используется, это не C++, где область видимости переменной ограничена парой скобок. В коде
0{
1 Obj obj= new Obj();
2 obj.Method();
3 this.Method()
4}

объект obj может попасть к сборщику мусора уже после выполнения второй строчки.
Но появляется новая ссылка на объект внутри метода?
Внутрь метода передается только объект типа IntPtr, у него нет ссылок на исходный объект wr.
Почитал хорошую статью, посвященную именно этой проблеме. Разобрался.
Дело в том, что объект wr становится доступным сборщику мусора сразу после начала выполнения метода DoCalculations, ведь больше нет ни одного «живого» объекта, кто на него ссылался бы.
Тут я бы посоветовал написать, что wr становится доступным для сборки мусора сразу после вызова unmanaged кода, и только при условии, что дальше wr нигде не используется внутри DoCalculations().
Вообще говоря, пример был бы намного понятнее, если бы DoCalculations() было как раз вызовом unmanaged кода, а не методом, содержащим такие вызовы.
Вот с этим соглашусь, все выглядит лаконично и верно!
В приведенной Вами статье описывается несколько иной (хотя и тоже интересный) случай — когда уничтожается объект, у которого вызывается метод. Здесь же на самом деле не суть как важно, что метод содержит неуправляемый код, главное что ему просто нужен валидный указатель.

>то wr становится доступным для сборки мусора сразу после вызова unmanaged кода, и только при условии, что дальше wr нигде не используется внутри DoCalculations()

Так DoCalculations и не имеет ссылки на wr, ему передается wr.Obj только.
Понятно, спасибо. Показалось, что параметром подавался весь wr. Меня ввело в заблуждение название класса: Wrapper. Кстати, непонятно, зачем создавать объект-обертку, если в метод подается только его проперти obj :)
Между прочим, не означает ли это, что obj может использоваться отдельно от Wrapper, и, следовательно, не принадлежит ему? Возможно, здесь проблема вообще из-за ошибки проектирования и Wrapper не должен очищать obj, так как связан с ним не композитивно, а аггрегативно.
Wrapper очищает Obj, потому как является его владельцем, он создает его, он его же и удаляет. Собственно Wrapper по сути дела является аналогом smart pointer, объект который просто хранит ресурс, и уничтожаясь сам уничтожает и ресурс. Поэтому тут как раз все нормально, просто программист, который писал код не подумал о том, что wr может быть собран сборщиком мусора до конца метода.
По поводу того, почему передается не обертка, а сам IntPtr — просто в коде реального приложения был метод принимающий именно IntPtr, и протянуть туда обертку простыми средствами было невозможно
+100500, по примеру не понятно, как такое может произойти!
Спасибо, познавательно.

А как решили проблему во второй истории?
Пришлось сделать дженериковый метод, в котором в качестве дженерикового параметра передавать тип из сборки, где лежат ресурсы. А по типу уже можно однозначно и правильно достать сборку.
вот все бы так писали примеры: коротко, ясно и всё по делу. Без лишнего кода. Спасибо
Первый пример уже практически классика. Он описан у Рихтера. А вот за второй пример — большое спасибо.

У меня, кстати, есть «околоплодный» вопрос, на который сам никак не найду ответ. А как всё-таки правильно поставить процесс разработки, чтобы отслеживать ошибки такой природы? Понимаю, что вопрос несколько нубский, но тем не менее. Мы на нашем проекте, к моему стыду, просто не выпускаем продукта в Release-варианте. Я знаю, что это как минимум «не очень хорошо», но раз такая возможность у нас есть, мы решили не усложнять себе жизнь с Release/Debug версиями.
Мне кажется, что кроме как «думать головой», делать ничего не остается :) Можно еще привлекать дополнительные головы посредством (обязательного) ревью кода.
Не воспринимайте лично, но это какой-то нехороший ответ. То есть даже не так. Я сам себе не позволяю так отвечать и регулярно думаю про описанную проблему. Дело в том, что «думать головой», конечно надо всегда (в этом я убеждён), просто нет возможности это обеспечить с гарантиями. Кроме того, даже если «думать головой постоянно», ошибок всё равно не избежать. В конце концов, я — человек и только и делаю, что ошибаюсь (ну, в перерывах между правильными решениями :) ).

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

Надеюсь, мой юношеский максимализм в стремлении к надёжному коду не сильно прёт из меня. :)
Если говорить о практиках выявления ошибок, то целенаправленно тестируют обычно только на наличие самых распространенных ошибок. Остальные могут выявиться хорошо поставленным процессом blackbox-тестирования.
Что до предотвращения ошибок, то здесь рулит опыт разработчиков.

Между прочим, еще можно попробовать использовать анализаторы кода навроде FxCop.
К сожалению, не существует «серебряной пули». И если число некоторого вида ошибок можно уменьшить соблюдая некоторые правила, то тут помочь может разве что опыт и внимательность программиста. Ну и стоит заметить, что совсем уж частыми вышеприведенные случаи назвать сложно.
Я так понимаю, что если обернуть Wrapper wr в using, то тем самым мы гарантируем существование объекта до конца блока using (т.к. в конце блока автоматически вызывается Dispose, вызов которого требует наличие объекта). То есть до конца блока функции не гарантируется, что объект не будет финализирован, а внутри блока using гарантируется, что объект не будет финализирован до конца блока. То есть конструкция using { x = new x();} разворачивается не в конструкцию
try { x = new x(); } finally { x.Dispose(); }
а в конструкцию
try { x = new x(); } finally { x.Dispose(); GC.KeepAlive(x); }

Это верно?
Нет, это не так. Using в анном случае даст следующий код:
try { x = new x(); } finally {if (x != null) ((IDisposable) x).Dispose(); }

То, что к объекту есть обращение в блоке finally автоматически сохраняет его в графе достижимых объектов до конца выполнения try-блока, поэтому вызов GC.KeepAlive(x) не нужен.
Любое использование объекта делает недоступным его для сборщика мусора.
Т.е. x.Dispose() и вообще x.Something() не даст уничтожить объект до этого метода.
В релизной версии в общем случае это может быть не совсем правдой. Компилятор может заинлайнить вызов метода x.Something(), и если внутри нет использования this (то есть метод по сути статический), то объект может быть уничтожен до вызова этого метода, если других якорей нет.
В этом коменте рекомендуется статья, которая говорит, что это не совсем так. Насколько я из нее понял, объект может быть финализирован даже во время выполнения его же метода, если в этом методе осуществляется обращение к неуправляемому коду.
UFO just landed and posted this here
Из какого издания? Я читал только по второму фреймворку и года три назад, такого примера там не помню.
UFO just landed and posted this here
Значит я успел позабыть с тех пор (:
Sign up to leave a comment.

Articles