Ответ 1
Ваша проблема не вызвана утечкой управляемой памяти. Очевидно, что вы щелкаете ошибку где-то в неуправляемом коде.
Метод SyncFlush() вызывается после нескольких вызовов MILCore и, как представляется, приводит к немедленной обработке изменений, которые были отправлены, вместо того, чтобы оставаться в очереди для последующей обработки. Поскольку вызов обрабатывает все ранее отправленные, ничто в вашем визуальном дереве не может быть исключено из отправляемого вами стека вызовов.
Стек вызовов, включающий неуправляемые вызовы, может оказаться более полезной. Запустите приложение под VS.NET с собственной отладкой или с помощью windbg или другого отладчика собственного кода. Установите отладчик для прерывания исключения и получите стек вызовов в относительной точке останова.
Стек вызовов, конечно, спустится в MILCore, и оттуда он может перейти на уровень DirectX и драйвер DirectX. Ключ о том, какая часть вашего кода вызвала проблему, может быть найдена где-то в этом наборе вызовов.
Скорее всего, MILCore передает огромное значение некоторого параметра в DirectX на основе того, что вы ему рассказываете. Проверьте приложение на все, что может вызвать ошибку, которая заставит DirectX выделять много памяти. Примеры вещей для поиска:
- Bitmap Источники, которые настроены на загрузку с очень высоким разрешением.
- Большие WritableBitmaps
- Чрезвычайно большие (или отрицательные) значения преобразования или размера
Другим способом атаки этой проблемы является постепенное упрощение вашего приложения до тех пор, пока проблема не исчезнет, а затем посмотрите очень внимательно на то, что вы удалили последним. Когда это удобно, это может быть полезно сделать в виде бинарного поиска: сначала вырезать половину визуальной сложности. Если он работает, верните половину того, что было удалено, иначе удалите еще одну половину. Повторяйте до конца.
Также обратите внимание, что обычно нет необходимости фактически удалять компоненты пользовательского интерфейса, чтобы не видеть MILCore. Любой Visual с Visibility.Hidden может быть полностью пропущен.
Существует не обобщенный способ избежать этой проблемы, но метод поиска поможет вам определить, что конкретно нужно изменить, чтобы исправить его в конкретном случае.
Можно безопасно сказать из стека вызовов, что вы обнаружили ошибку в .NET Framework или драйверах DirectX для конкретной видеокарты.
Что касается второй трассировки стека, опубликованной
Джон Кноллер прав, что переход от RtlFreeHeap к ConvertToUnicode нонсенс, но делает неправильный вывод из него. Мы видим, что ваш отладчик потерялся при отслеживании стека. Он начал корректно из исключения, но потерялся ниже фреймаAssembly.ExecuteMainMethod
, потому что эта часть стека была перезаписана при обработке исключения и отладчика.
К сожалению, любой анализ этой трассировки стека бесполезен для ваших целей, потому что он был захвачен слишком поздно. То, что мы видим, является исключением, возникающим при обработке WM_LBUTTONDOWN, которая преобразуется в WM_SYSCOMMAND, которая затем выдает исключение. Другими словами, вы нажали на то, что вызвало системную команду (например, изменение размера), что вызвало исключение. В момент, когда эта трассировка стека была захвачена, исключение уже обрабатывалось. Причина, по которой вы видите вызовы User32 и UxTheme, заключается в том, что они связаны с обработкой нажатия кнопки. Они не имеют ничего общего с реальной проблемой.
Вы находитесь на правильном пути, но вам нужно будет захватить трассировку стека в момент сбоя распределения (или вы можете использовать один из других подходов, предложенных выше).
Вы узнаете, что у вас есть правильная трассировка стека, когда в ней отображаются все управляемые фреймы в вашей первой трассировке стека, а верхняя часть стека является сбоем в распределении памяти. Обратите внимание, что нас действительно интересуют только неуправляемые кадры, которые появляются над вызовом DUCE+Channel.SyncFlush
- все ниже, чем будет .NET Framework и код вашего приложения.
Как получить собственную трассировку стека в нужное время
Вы хотите получить трассировку стека во время первого отказа выделения памяти в показанном вызове DUCE+Channel.SyncFlush
. Это может быть сложно. Есть три подхода, которые я использую: (обратите внимание, что в каждом случае вы начинаете с точки останова внутри вызова SyncFlush - см. Примечание ниже для более подробной информации)
-
Установите отладчик для разрыва всех исключений (управляемых и неуправляемых), затем продолжайте движение (F5 или "g" ) до тех пор, пока он не сломается на выделение выделения памяти, которое вас интересует. Это первое, что нужно сделать чтобы попытаться, потому что это быстро, но часто не работает при работе с собственным кодом, потому что собственный код часто возвращает код ошибки в вызывающий собственный код вместо того, чтобы бросать исключение.
-
Установите отладчик для разрыва всех исключений, а также установите точки останова в общих подпрограммах распределения памяти, затем нажмите F5 (go) несколько раз, пока не произойдет исключение, подсчитав, сколько F5s вы нажмете. В следующий раз, когда вы запустите, используйте еще одно F5, и вы можете быть в вызове выделения, который сгенерировал исключение. Захватите стек вызовов в Блокнот, затем снова нажмите F10 (перейдите), чтобы увидеть, действительно ли это было выделением.
-
Установите точку останова в первом собственном фрейме, который вызвал SyncFlush (это wpfgfx_v0300! MilComposition_SyncFlush), чтобы пропустить переход с управляемым на нативный, а затем F5 для запуска. F10 (перейдите) через функцию до EAX содержит один из кодов ошибок E_OUTOFMEMORY (0x8007000E), ERROR_OUTOFMEMORY (0x0000000E) или ERROR_NOT_ENOUGH_MEMORY (0x0000008). Обратите внимание на самую последнюю инструкцию "Вызов". В следующий раз, когда вы запустите программу, бегите туда и входите в нее. Повторяйте это до тех пор, пока вы не перейдете к вызову выделения памяти, вызвавшему проблему, и дамп трассировки стека. Обратите внимание, что во многих случаях вы столкнетесь с довольно сложной структурой данных, поэтому необходим определенный интеллект, чтобы установить соответствующую точку останова, чтобы пропустить цикл, чтобы вы могли быстро добраться туда, где нужно. Этот метод очень надежный, но очень трудоемкий.
Примечание. В каждом случае вы не хотите устанавливать точки останова или запускать одноэтапный режим, пока ваше приложение не окажется внутри отказавшего вызова DUCE+Channel.SyncFlush
. Чтобы обеспечить это, запустите приложение, когда все точки останова отключены. Когда он запущен, включите точку останова на System.Windows.Media.Composition.DUCE+Channel.SyncFlush
и измените размер окна. В первый раз просто нажмите F5, чтобы исключить ошибку при первом вызове SyncFlush (если нет, подсчитайте, сколько раз вам нужно ударить F5 до возникновения исключения). Затем отключите точку останова и перезапустите программу. Повторите процедуру, но на этот раз после того, как вы нажмете вызов SyncFlush в нужное время, установите точки останова или выполните однократное нажатие, как описано выше.
Рекомендации
Методы отладки, описанные выше, являются трудоемкими: планируйте провести как минимум несколько часов. Из-за этого я обычно многократно пытаюсь упростить свое приложение, чтобы узнать, что именно щекочет ошибку перед тем, как перейти в отладчик для чего-то вроде этого. Это имеет два преимущества: он даст вам хороший реестр для отправки поставщику видеокарты, и он сделает вашу отладку быстрее, потому что будет меньше отображаться и, следовательно, меньше кода для одноэтапного доступа, меньше распределений и т.д.
Поскольку проблема возникает только с конкретной графической картой, нет сомнений в том, что проблема заключается либо в ошибке в драйвере графической карты, либо в коде MilCore, который ее вызывает. Скорее всего, это драйвер видеокарты, но возможно, что MilCore передает недопустимые значения, которые обрабатываются на большинстве графических карт, но не в этом. Методы отладки, описанные выше, скажут вам следующее: например, если MilCore сообщает графической карте о распределении области 1000000x1000000 пикселей, а графическая карта дает правильную информацию о разрешении, ошибка находится в MilCore. Но если запросы MilCore разумны, ошибка возникает в драйвере графической карты.