Итак, привожу решение проблемы из топика habrahabr.ru/blogs/net/69545 — про гарантированное освобождение неуправляемого ресурса.
Как правильно заметил товарищ adontz, проблема решается при помощи CER.
CER — это технология Constrained Execution Region, при помощи которой можно обезопасить свой код, гарантировав ему то, что на некотором его участке гарантированно _не_ случится асинхронного прерывания типа StackOverflow или ThreadAbort, или «отвалился» jitter — всякое бывает, и не всегда есть возможность оставить внешние ресурсы в консистентном состоянии.
Другими словами, мы можем «форсировать» рантайм на то, чтобы заблокировать участок код и подготовить все для его выполнения: проверить, что нам хватит памяти на стеке и в оперативке, за-jitt-ить код, и затем — рраз! — выполнить подготовленный код, а потом разобраться, не случилось ли чего.
Чтобы исполнение кода не «отвалилось» в процессе, рантайм накладывает некоторые ограничения на то, чего _нельзя_ делать в регионе CER. И, в общем-то, просто так нельзя делать много чего:
Итак, вернемся к нашимбаранам тяжелым будням, т.е. перейдем к примерам. Чтобы воспользоваться CER на практике, нужно копать в сторону класса-хелпера
Его наиболее важный для нас метод —
Для проверки — такой тест:
Программа работает и не завершается. Если теперь закомментировать try-finally, то программа закончится. Совсем — запрос на ThreadAbortException _не_ будет отложен до конца выхода из блока finally (которого не происходит из-за бесконечного цикла while(true){}), а сработает сразу же (почти сразу же)
Теперь мы знаем, как «починить» наш код из примера с IntPtr:
На будущее — MSDN советует избегать использования неуправляемых ресурсов. Несколько странный совет в свете того, что одна из целей создания CLR — как раз-таки взаимодействие с унаследованным неуправляемым кодом и ресурсами.
Так что я советую внимательно смотреть на места работы с неуправляемыми ресурсами — практика показывает, что даже в полностью работающей программе в этих точках можно отыскать 2-3 серьезных бага.
Понятно, что я разобрал только то, что помогло мне разобраться с утечкой памяти; но в самом CER я не затронул вот что: контракты на методы, годные для CER, подготовка events для вызова внутри CER, подготовка виртуальных методов, использование Critical Finalizers, FailFast и MemoryBarriers для надежной работы программы и предотвращения утечек неуправляемых ресурсов. Надо ли писать об этом или отослать в MSDN?
Как правильно заметил товарищ adontz, проблема решается при помощи CER.
CER — это технология Constrained Execution Region, при помощи которой можно обезопасить свой код, гарантировав ему то, что на некотором его участке гарантированно _не_ случится асинхронного прерывания типа StackOverflow или ThreadAbort, или «отвалился» jitter — всякое бывает, и не всегда есть возможность оставить внешние ресурсы в консистентном состоянии.
Того!
Другими словами, мы можем «форсировать» рантайм на то, чтобы заблокировать участок код и подготовить все для его выполнения: проверить, что нам хватит памяти на стеке и в оперативке, за-jitt-ить код, и затем — рраз! — выполнить подготовленный код, а потом разобраться, не случилось ли чего.
Чтобы исполнение кода не «отвалилось» в процессе, рантайм накладывает некоторые ограничения на то, чего _нельзя_ делать в регионе CER. И, в общем-то, просто так нельзя делать много чего:
- исполнять MSIL-oпкод newobj;
- выполнять боксинг (как мы знаем, он неявно вызывает newobj) (unboxing — можно);
- вызывать виртуальные методы (если они явно не были подготовлены — как, покажу если надо);
- вызывать методы через Reflection и обращаться к полям через Transparent Proxies;
- использовать сериализацию;
- использовать многомерные массивы(!);
Итак, вернемся к нашим
System.Runtime.CompilerServices.RuntimeHelpers
. Его наиболее важный для нас метод —
PrepareConstrainedRegions
, и выполняет описанную подготовку — например, проходит по _будущему_ стеку вызовов, выполняя pre-jitt и расставляя stop-the-world отметки (теперь понятно, почему нельзя «в лоб» использовать виртуальные методы и интерфейсы — там динамическая диспетчеризация, и стека вызовов составить невозможно до момента реального исполнения кода). Теперь самое интересное. Использование этого метода должно выглядеть так:Именно так — пустой блок try, и все непрерываемые операции должны идти в блоке finally!... System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); try {} finally { // atomic-операции должны идти здесь } ...
Для проверки — такой тест:
Thread t = new Thread(new ThreadStart( () => { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { while (true) { } }})); t.Start(); Thread.Sleep(1000); t.Abort(); t.Join();
Программа работает и не завершается. Если теперь закомментировать try-finally, то программа закончится. Совсем — запрос на ThreadAbortException _не_ будет отложен до конца выхода из блока finally (которого не происходит из-за бесконечного цикла while(true){}), а сработает сразу же (почти сразу же)
Теперь мы знаем, как «починить» наш код из примера с IntPtr:
IntPtr ptr = IntPtr.Zero; try { //... System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { ptr = Marshal.AllocHGlobal(500); } // ..работаем, работаем.. } finally { if( ptr != IntPtr.Zero ) Marshal.FreeHGlobal(ptr); } ...
На будущее — MSDN советует избегать использования неуправляемых ресурсов. Несколько странный совет в свете того, что одна из целей создания CLR — как раз-таки взаимодействие с унаследованным неуправляемым кодом и ресурсами.
Так что я советую внимательно смотреть на места работы с неуправляемыми ресурсами — практика показывает, что даже в полностью работающей программе в этих точках можно отыскать 2-3 серьезных бага.
Понятно, что я разобрал только то, что помогло мне разобраться с утечкой памяти; но в самом CER я не затронул вот что: контракты на методы, годные для CER, подготовка events для вызова внутри CER, подготовка виртуальных методов, использование Critical Finalizers, FailFast и MemoryBarriers для надежной работы программы и предотвращения утечек неуправляемых ресурсов. Надо ли писать об этом или отослать в MSDN?