TL; DR: Вероятно, лучшим решением было бы создание небольшого пула WriteableBitmaps
и его повторное использование, а не их создание и выбрасывание.
Поэтому я начал разбираться с WinDbg, чтобы посмотреть, что послужило причиной возникновения коллекций.
Сначала я добавил вызов Debugger.Break()
к началу Main
, чтобы упростить процесс. Я также добавил свой собственный вызов к GC.Collect()
в качестве проверки работоспособности, чтобы убедиться, что моя точка останова работает нормально. Тогда в WinDbg:
0:000> .loadby sos clr
0:000> !bpmd mscorlib.dll System.GC.Collect
Found 3 methods in module 000007feee811000...
MethodDesc = 000007feee896cb0
Setting breakpoint: bp 000007FEEF20E0C0 [System.GC.Collect(Int32)]
MethodDesc = 000007feee896cc0
Setting breakpoint: bp 000007FEEF20DDD0 [System.GC.Collect()]
MethodDesc = 000007feee896cd0
Setting breakpoint: bp 000007FEEEB74A80 [System.GC.Collect(Int32, System.GCCollectionMode)]
Adding pending breakpoints...
0:000> g
Breakpoint 1 hit
mscorlib_ni+0x9fddd0:
000007fe`ef20ddd0 4154 push r12
0:000> !clrstack
OS Thread Id: 0x49c (0)
Child SP IP Call Site
000000000014ed58 000007feef20ddd0 System.GC.Collect()
000000000014ed60 000007ff00140388 ConsoleApplication1.Program.Main(System.String[])
Таким образом, точка останова работала нормально, но когда я позволил программе продолжиться, она никогда больше не выполнялась. Казалось, рутина GC была вызвана откуда-то глубже. Затем я вошел в функцию GC.Collect()
, чтобы увидеть, что она вызывает. Чтобы сделать это проще, я добавил второй вызов к GC.Collect()
сразу после первого и вошел во второй. Это позволило избежать перебора всей компиляции JIT:
Breakpoint 1 hit
mscorlib_ni+0x9fddd0:
000007fe`ef20ddd0 4154 push r12
0:000> p
mscorlib_ni+0x9fddd2:
000007fe`ef20ddd2 4155 push r13
0:000> p
...
0:000> p
mscorlib_ni+0x9fde00:
000007fe`ef20de00 4c8b1d990b61ff mov r11,qword ptr [mscorlib_ni+0xe9a0 (000007fe`ee81e9a0)] ds:000007fe`ee81e9a0={clr!GCInterface::Collect (000007fe`eb976100)}
После небольшого шага я заметил ссылку на clr!GCInterface::Collect
, которая звучала многообещающе. К сожалению, точка останова на нем никогда не сработала. Копаясь дальше в GC.Collect()
, я нашел clr!WKS::GCHeap::GarbageCollect
, который оказался реальным методом. Точка останова показала код, который запускал коллекцию:
0:009> bp clr!WKS::GCHeap::GarbageCollect
0:009> g
Breakpoint 4 hit
clr!WKS::GCHeap::GarbageCollect:
000007fe`eb919490 488bc4 mov rax,rsp
0:006> !clrstack
OS Thread Id: 0x954 (6)
Child SP IP Call Site
0000000000e4e708 000007feeb919490 [NDirectMethodFrameStandalone: 0000000000e4e708] System.GC._AddMemoryPressure(UInt64)
0000000000e4e6d0 000007feeeb9d4f7 System.GC.AddMemoryPressure(Int64)
0000000000e4e7a0 000007fee9259a4e System.Windows.Media.SafeMILHandle.UpdateEstimatedSize(Int64)
0000000000e4e7e0 000007fee9997b97 System.Windows.Media.Imaging.WriteableBitmap..ctor(Int32, Int32, Double, Double, System.Windows.Media.PixelFormat, System.Windows.Media.Imaging.BitmapPalette)
0000000000e4e8e0 000007ff00141f92 ConsoleApplication1.Program.<Main>b__c(Int32)
Итак, конструктор WriteableBitmap
косвенно вызывает GC.AddMemoryPressure , что в итоге приводит к коллекциям (кстати, GC.AddMemoryPressure
- более простой способ симулировать использование памяти). Это не объясняет внезапного изменения поведения при переходе от размера 33 к 32.
ILSpy помогает здесь. В частности, если вы посмотрите на конструктор для SafeMILHandleMemoryPressure
(вызывается SafeMILHandle.UpdateEstimatedSize
), вы увидите, что он использует GC.AddMemoryPressure
, только если добавляемое давление <= 8192. В противном случае он использует собственную систему для отслеживания давление памяти и запуск коллекций. Размер растрового изображения 32x32 с 32-битными пикселями подпадает под этот предел, потому что <code>WriteableBitmap оценивает использование памяти как 32 * 32 * 4 * 2 (я не уверен, почему там есть дополнительный фактор 2).
Таким образом, похоже, что поведение, которое вы видите, является результатом эвристики в рамках, которая не очень хорошо работает для вашего случая. Возможно, вам удастся обойти это, создав растровое изображение с большими размерами или большим пиксельным форматом, чем вам нужно, чтобы предполагаемый объем памяти растрового изображения составил> 8192.
Запоздалая мысль: Полагаю, это также говорит о том, что коллекции, инициированные в результате GC.AddMemoryPressure
, учитываются в "# Induced GC"?