Класс растрового изображения не удаляет поток?

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

Глядя на исходный код, классы Bitmap и Image создают экземпляр GPStream для обертывания потока, но не хранят ссылку ни на GPStream, ни на экземпляр Stream.

num = SafeNativeMethods.Gdip.GdipLoadImageFromStreamICM(new GPStream(stream), out zero);

Теперь класс GPStream (внутренний) не реализует метод Release или Dispose - ничего, что позволило бы GDI закрыть или удалить поток. А поскольку класс Image / Bitmap не хранит ссылку на экземпляр GPStream, кажется, у GDI, Drawing.Bitmap или Drawing.Stream нет абсолютно никакого способа правильно закрыть поток. .

Я мог бы создать подкласс Bitmap, чтобы исправить это, но, подождите, он запечатан.

Пожалуйста, скажите мне, что я ошибаюсь, и что MS не просто сделала невозможным написать код, который не пропускает ресурсы с их API.

Имейте в виду: (а) Bitmap не имеет никакой управляемой ссылки на поток, что означает, что GC будет собирать его, пока он все еще используется, и (b) API-интерфейсы .NET принимают ссылки Bitmap / Image и не используют ' t детерминирован относительно того, когда они с ними покончили.


person Lilith River    schedule 25.05.2011    source источник
comment
Да ... с тех пор, как я наткнулся на этот беспорядок, я просто глубоко клонирую все изображения, которые открываю, используя LockBits и Marshal.Copy. Единственный способ убедиться в этом - начать с Bitmap объекта, созданного без каких-либо связанных ресурсов.   -  person Nyerguds    schedule 28.01.2018
comment
@Nyerguds, это действительно хорошее решение. Однако на этом этапе я бы рекомендовал по возможности не использовать API System.Drawing. Это просто заноза в заднице, особенно в отношении управления жизненным циклом объекта, и утечки так легко случайно создать.   -  person AsPas    schedule 24.01.2021
comment
@AsP К тому же, управление объектами - это просто образ мышления. Вполне возможно проявить достаточно дотошности, чтобы никогда не было утечек памяти. В таких языках, как C ++, в любом случае нет ничего похожего на автоматическую сборку мусора. Но да, System.Drawing в наши дни как бы показывает свой возраст, и его нельзя использовать повсеместно.   -  person Nyerguds    schedule 24.01.2021
comment
Я написал Imageflow.NET, чтобы не использовать System.Drawing. См. github.com/imazen/imageflow-dotnet.   -  person Lilith River    schedule 25.01.2021
comment
@LilithRiver ну, если вы используете Windows.Forms и хотите отображать изображения в общих элементах управления Windows, вы все равно застряли с System.Drawing ...   -  person Nyerguds    schedule 16.02.2021


Ответы (3)


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

person C.Evenhuis    schedule 25.05.2011
comment
Точно. Это обсуждалось ранее - Bitmap не несет ответственности за время жизни потока. Что, если вы захотите сделать что-то еще с тем же потоком после игры с Bitmap? Вы были бы очень разочарованы, узнав, что Bitmap разместил его за вас. - person n8wrl; 25.05.2011
comment
Да, если конструктор доработал поток. Но когда объект «берет» поток и использует его в произвольное время, право собственности передается. В этом случае только класс Bitmap знает, когда он «готов» с потоком, и поэтому только класс Bitmap может нести ответственность за его закрытие. - person Lilith River; 25.05.2011
comment
Обычно объект делает собственную ссылку на поток, предотвращая его сборку мусора до тех пор, пока это необходимо, но не удаляет его, так как это приведет к нечитаемому коду и, возможно, даже не отслеживаемым, случается один раз в синей луне ошибки . - person C.Evenhuis; 25.05.2011
comment
В идеале он считывал бы все в память, и вы могли бы удалить поток сразу после его чтения. Очевидно, он сохраняет поток открытым для чтения метаданных по запросу. Довольно жалкое оправдание. - person Lilith River; 25.05.2011
comment
Он также не может сохранить ссылку на поток, поэтому поток собирает мусор, пока он все еще используется (поскольку ссылка передается обратно через COM). Было ТАК сообщений об этой проблеме. - person Lilith River; 25.05.2011
comment
Растровое изображение - это задокументированное исключение из правила, и, возможно, ненужное - я с этим не возражаю. Я не согласен с тем, чтобы Bitmap удалял поток, что делало его еще худшим объектом для работы. - person C.Evenhuis; 25.05.2011
comment
Что ж, если либо (а) он может загрузить его на 100% в память, а не только на 99% в память, (б) не быть запечатанным, (в) предложить событие удаления, (г) иметь логическое значение для закрытия потока при удалении, или (e) просто сохраняйте вонючую ссылку на поток, чтобы сборщик мусора работал правильно, с ним было бы намного проще работать. - person Lilith River; 25.05.2011

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

person Emond Erno    schedule 25.05.2011
comment
Передача экземпляра Bitmap и (необязательного) потока довольно болезненна. Глядя на все API, которые я видел, никто не ожидает такого поведения. - person Lilith River; 25.05.2011
comment
@ComputerLinguist - Если вы чувствуете необходимость передать эти два понятия, ваш дизайн может быть неправильным. В таком случае я бы попытался сделать объект (а не отдельный метод) ответственным за открытие и закрытие потока. - person Emond Erno; 27.07.2013
comment
Учтите, что сами потоки можно настроить для закрытия родительского потока, а сам Bitmap имеет поведение потоковой передачи. - person Lilith River; 27.07.2013

Поскольку растровое изображение не может гарантировать, в каком порядке вызывается деструктор, он не закроет поток, потому что он, возможно, уже был закрыт собственным деструктором во время сборки мусора. В CLR Джеффри Рихтера через C # есть глава, посвященная управлению памятью, которая объясняет гораздо яснее, чем я.

person Ryan    schedule 25.05.2011
comment
Собственно, может. Bitmap будет генерировать случайные исключения COM, если вы закроете базовый поток перед удалением Bitmap. Передайте MemoryStream новому экземпляру Bitmap, немного подождите, получите доступ к bitmap.Width и посмотрите, как летают исключения. - person Lilith River; 27.07.2013