О чем речь?
Ваша программа наконец заработала, заказчик на тестовом полигоне уверенно тыкает кнопки, все работает штатно… но не спешите радоваться: вас могут подстерегать проблемы после выхода release версии. ПО начинает сыпаться с непонятными багами, заказчик уже готов порвать вас на британский флаг…
Думаете это проблема только Си-шников? Вы ошибаетесь!
Практический пример
Обыкновенно вы привыкли к тому, что на тест полигон вы выкладываете дебаг версию (это, конечно, не относится ко всем, но, поверьте, так бывает очень часто). Это экономит время, да и отладочной информации больше, можно зацепиться удаленной отладкой…
Казалось бы, бремя выпуска релиза Вас уже мало касается, все же .NET это чуточку улучшенный C++ в котором существуют определенные проблемы. Но все не так безоблачно, как может казаться на первых порах.
Итак, пример:
- static void Main(string[] args)
- {
- System.Threading.Timer t = new System.Threading.Timer(state =>
- {
- Console.WriteLine("Timer!");
-
- GC.Collect();
- }, null, 0, 1000);
-
- Console.ReadKey();
- }
* This source code was highlighted with Source Code Highlighter.
Простейший код, который по задумке автора должен писать заветное «Timer!» в консоль раз в секунду. Отладка в собранном в debug конфигурации проекте ничего подозрительного не выявляет, и подготавливается релиз… в котором программа не работает.
Разбор полётов
Да, да, этот «баг» является непосредственным следствием оптимизации (ну и, конечно, небольшой халатности программиста). Халатность заключена в том, что указанный System.Threading.Timer на самом деле наследует IDisposable, и, по хорошему, мы должны бы вызывать для него Dispose() метод. Ну пусть мы этого не делаем (на самом деле это к делу и не относится).
Изучаем листинг IL кода, сгенерированного в релиз и дебаг версиях и видим вот что.
дебаг:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 5
.locals init (
[0] class [mscorlib]System.Threading.Timer t)
L_0000: nop
L_0001: ldsfld class
[mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<>9__CachedAnonymousMethodDelegate1
....
и релиз:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: ldsfld class [mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<>9__CachedAnonymousMethodDelegate1
....
Я выделил различие.
Для тех кто еще не догадался
В дебаг версии компилятор оставляет нигде не используемую (кроме объявления) ссылку Timer в стеке, и GC видя, что ссылка жива (до тех пор пока не закончился метод) ничего не делает. Таймер работает.
В релиз же версии ссылка нигде не существует и первый же (не гарантируется) метод GC.Collect() уничтожит таймер.
Послесловие
Я искренне надеюсь что вам никогда не придется попадаться на явные (или нет) баги оптимизаторов, как в приведенном выше примере, в реальной жизни. Поиск подобных багов (особенно на стороне заказчика) очень сложен и порекомендовать я могу только сборку дебаг версии всех компонент и постепенной компиляции частей в релизе с целью поиска «бракованной» сборки и дальнейший ее анализ. Также может помочь распространение дебаг информации (.pdb файл обычно).
Удачной пятницы, ведите логи 8-)