Комментарии 27
Круто, спасибо, что поделились опытом!
В первом примере не совсем понятно, почему wr становится доступным сборщику мусора сразу же после вызова calc.DoCalculations(). DoCalculations() вызывает unmanaged код, да еще и асинхронно? В таком случае, стоило бы об этом упомянуть.
Или, возможно, я чего-то недопонял :)
В первом примере не совсем понятно, почему wr становится доступным сборщику мусора сразу же после вызова calc.DoCalculations(). DoCalculations() вызывает unmanaged код, да еще и асинхронно? В таком случае, стоило бы об этом упомянуть.
Или, возможно, я чего-то недопонял :)
+1
calc.DoCalculations() действительно вызывает неуправляемый код (а зачем ему еще указатель?), но все происходит синхронно. А wr попадает к сборщику мусора потому что грубо говоря больше нигде не используется, это не C++, где область видимости переменной ограничена парой скобок. В коде
объект obj может попасть к сборщику мусора уже после выполнения второй строчки.
0{
1 Obj obj= new Obj();
2 obj.Method();
3 this.Method()
4}
объект obj может попасть к сборщику мусора уже после выполнения второй строчки.
0
Но появляется новая ссылка на объект внутри метода?
0
Почитал хорошую статью, посвященную именно этой проблеме. Разобрался.
Вообще говоря, пример был бы намного понятнее, если бы DoCalculations() было как раз вызовом unmanaged кода, а не методом, содержащим такие вызовы.
Дело в том, что объект wr становится доступным сборщику мусора сразу после начала выполнения метода DoCalculations, ведь больше нет ни одного «живого» объекта, кто на него ссылался бы.Тут я бы посоветовал написать, что wr становится доступным для сборки мусора сразу после вызова unmanaged кода, и только при условии, что дальше wr нигде не используется внутри DoCalculations().
Вообще говоря, пример был бы намного понятнее, если бы DoCalculations() было как раз вызовом unmanaged кода, а не методом, содержащим такие вызовы.
0
Вот с этим соглашусь, все выглядит лаконично и верно!
0
В приведенной Вами статье описывается несколько иной (хотя и тоже интересный) случай — когда уничтожается объект, у которого вызывается метод. Здесь же на самом деле не суть как важно, что метод содержит неуправляемый код, главное что ему просто нужен валидный указатель.
>то wr становится доступным для сборки мусора сразу после вызова unmanaged кода, и только при условии, что дальше wr нигде не используется внутри DoCalculations()
Так DoCalculations и не имеет ссылки на wr, ему передается wr.Obj только.
>то wr становится доступным для сборки мусора сразу после вызова unmanaged кода, и только при условии, что дальше wr нигде не используется внутри DoCalculations()
Так DoCalculations и не имеет ссылки на wr, ему передается wr.Obj только.
+1
Понятно, спасибо. Показалось, что параметром подавался весь wr. Меня ввело в заблуждение название класса: Wrapper. Кстати, непонятно, зачем создавать объект-обертку, если в метод подается только его проперти obj :)
Между прочим, не означает ли это, что obj может использоваться отдельно от Wrapper, и, следовательно, не принадлежит ему? Возможно, здесь проблема вообще из-за ошибки проектирования и Wrapper не должен очищать obj, так как связан с ним не композитивно, а аггрегативно.
Между прочим, не означает ли это, что obj может использоваться отдельно от Wrapper, и, следовательно, не принадлежит ему? Возможно, здесь проблема вообще из-за ошибки проектирования и Wrapper не должен очищать obj, так как связан с ним не композитивно, а аггрегативно.
0
Wrapper очищает Obj, потому как является его владельцем, он создает его, он его же и удаляет. Собственно Wrapper по сути дела является аналогом smart pointer, объект который просто хранит ресурс, и уничтожаясь сам уничтожает и ресурс. Поэтому тут как раз все нормально, просто программист, который писал код не подумал о том, что wr может быть собран сборщиком мусора до конца метода.
По поводу того, почему передается не обертка, а сам IntPtr — просто в коде реального приложения был метод принимающий именно IntPtr, и протянуть туда обертку простыми средствами было невозможно
По поводу того, почему передается не обертка, а сам IntPtr — просто в коде реального приложения был метод принимающий именно IntPtr, и протянуть туда обертку простыми средствами было невозможно
0
+100500, по примеру не понятно, как такое может произойти!
0
Спасибо, познавательно.
А как решили проблему во второй истории?
А как решили проблему во второй истории?
+4
вот все бы так писали примеры: коротко, ясно и всё по делу. Без лишнего кода. Спасибо
+5
Первый пример уже практически классика. Он описан у Рихтера. А вот за второй пример — большое спасибо.
У меня, кстати, есть «околоплодный» вопрос, на который сам никак не найду ответ. А как всё-таки правильно поставить процесс разработки, чтобы отслеживать ошибки такой природы? Понимаю, что вопрос несколько нубский, но тем не менее. Мы на нашем проекте, к моему стыду, просто не выпускаем продукта в Release-варианте. Я знаю, что это как минимум «не очень хорошо», но раз такая возможность у нас есть, мы решили не усложнять себе жизнь с Release/Debug версиями.
У меня, кстати, есть «околоплодный» вопрос, на который сам никак не найду ответ. А как всё-таки правильно поставить процесс разработки, чтобы отслеживать ошибки такой природы? Понимаю, что вопрос несколько нубский, но тем не менее. Мы на нашем проекте, к моему стыду, просто не выпускаем продукта в Release-варианте. Я знаю, что это как минимум «не очень хорошо», но раз такая возможность у нас есть, мы решили не усложнять себе жизнь с Release/Debug версиями.
+1
Мне кажется, что кроме как «думать головой», делать ничего не остается :) Можно еще привлекать дополнительные головы посредством (обязательного) ревью кода.
+3
Не воспринимайте лично, но это какой-то нехороший ответ. То есть даже не так. Я сам себе не позволяю так отвечать и регулярно думаю про описанную проблему. Дело в том, что «думать головой», конечно надо всегда (в этом я убеждён), просто нет возможности это обеспечить с гарантиями. Кроме того, даже если «думать головой постоянно», ошибок всё равно не избежать. В конце концов, я — человек и только и делаю, что ошибаюсь (ну, в перерывах между правильными решениями :) ).
Я же очень хотел услышать, существуют ли какие-то практики, помогающие выявить наличие таких ошибок (даже без их изоляции/локализации).
Надеюсь, мой юношеский максимализм в стремлении к надёжному коду не сильно прёт из меня. :)
Я же очень хотел услышать, существуют ли какие-то практики, помогающие выявить наличие таких ошибок (даже без их изоляции/локализации).
Надеюсь, мой юношеский максимализм в стремлении к надёжному коду не сильно прёт из меня. :)
0
Если говорить о практиках выявления ошибок, то целенаправленно тестируют обычно только на наличие самых распространенных ошибок. Остальные могут выявиться хорошо поставленным процессом blackbox-тестирования.
Что до предотвращения ошибок, то здесь рулит опыт разработчиков.
Между прочим, еще можно попробовать использовать анализаторы кода навроде FxCop.
Что до предотвращения ошибок, то здесь рулит опыт разработчиков.
Между прочим, еще можно попробовать использовать анализаторы кода навроде FxCop.
+3
К сожалению, не существует «серебряной пули». И если число некоторого вида ошибок можно уменьшить соблюдая некоторые правила, то тут помочь может разве что опыт и внимательность программиста. Ну и стоит заметить, что совсем уж частыми вышеприведенные случаи назвать сложно.
0
Я так понимаю, что если обернуть 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); }
Это верно?
try { x = new x(); } finally { x.Dispose(); }
а в конструкцию
try { x = new x(); } finally { x.Dispose(); GC.KeepAlive(x); }
Это верно?
0
Нет, это не так. Using в анном случае даст следующий код:
try { x = new x(); } finally {if (x != null) ((IDisposable) x).Dispose(); }
То, что к объекту есть обращение в блоке finally автоматически сохраняет его в графе достижимых объектов до конца выполнения try-блока, поэтому вызов GC.KeepAlive(x) не нужен.
try { x = new x(); } finally {if (x != null) ((IDisposable) x).Dispose(); }
То, что к объекту есть обращение в блоке finally автоматически сохраняет его в графе достижимых объектов до конца выполнения try-блока, поэтому вызов GC.KeepAlive(x) не нужен.
+2
Любое использование объекта делает недоступным его для сборщика мусора.
Т.е. x.Dispose() и вообще x.Something() не даст уничтожить объект до этого метода.
Т.е. x.Dispose() и вообще x.Something() не даст уничтожить объект до этого метода.
0
В релизной версии в общем случае это может быть не совсем правдой. Компилятор может заинлайнить вызов метода x.Something(), и если внутри нет использования this (то есть метод по сути статический), то объект может быть уничтожен до вызова этого метода, если других якорей нет.
+1
В этом коменте рекомендуется статья, которая говорит, что это не совсем так. Насколько я из нее понял, объект может быть финализирован даже во время выполнения его же метода, если в этом методе осуществляется обращение к неуправляемому коду.
0
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Пара историй про отличия Release от Debug