Ваша проблема не связана с управляемой утечкой памяти. Очевидно, вы где-то исправляете ошибку в неуправляемом коде.
Метод SyncFlush () вызывается после нескольких вызовов MILCore и, по-видимому, приводит к немедленной обработке отправленных изменений, а не остается в очереди для последующей обработки. Поскольку вызов обрабатывает все ранее отправленные сообщения, ничто в вашем визуальном дереве не может быть исключено из отправленного вами стека вызовов.
Стек вызовов, который включает неуправляемые вызовы, может содержать более полезную информацию. Запустите приложение под VS.NET с собственной отладкой или с помощью windbg или другого отладчика собственного кода. Установите для отладчика прерывание на исключение и получите стек вызовов в относительной точке останова.
Стек вызовов, конечно, спустится в MILCore, и оттуда он может перейти в уровень DirectX и драйвер DirectX. Подсказка о том, какая часть вашего кода вызвала проблему, может быть найдена где-то в этом собственном стеке вызовов.
Скорее всего, MILCore передает огромное значение какого-либо параметра в DirectX в зависимости от того, что вы говорите. Проверьте свое приложение на наличие всего, что может вызвать ошибку, из-за которой DirectX выделяет много памяти. Примеры вещей, которые нужно искать:
- BitmapSources, настроенные на загрузку с очень высоким разрешением.
- Большие записываемые битмапы
- Чрезвычайно большие (или отрицательные) значения преобразования или размера
Другим способом решения этой проблемы является постепенное упрощение приложения до тех пор, пока проблема не исчезнет, а затем очень внимательно изучите то, что вы удалили последним. Когда это удобно, это может быть полезно сделать в виде бинарного поиска: изначально вырежьте половину визуальной сложности. Если это работает, верните половину того, что было удалено, в противном случае удалите другую половину. Повторяйте, пока не закончите.
Также обратите внимание, что обычно нет необходимости фактически удалять компоненты пользовательского интерфейса, чтобы потом не видеть MILCore. Любой визуал с видимостью. Скрытый может быть полностью пропущен.
Не существует обобщенного способа избежать этой проблемы, но метод поиска поможет вам точно определить, что конкретно нужно изменить, чтобы исправить это в конкретном случае.
Можно с уверенностью сказать из стека вызовов, что вы обнаружили ошибку либо в NET Framework, либо в драйверах DirectX для конкретной видеокарты.
Относительно второй трассировки стека, которую вы выложили
Джон Ноллер прав, что переход от RtlFreeHeap к ConvertToUnicode - нонсенс, но из этого он делает неправильный вывод. Мы видим, что ваш отладчик потерялся при отслеживании стека. Он правильно запустился из исключения, но затерялся ниже фрейма Assembly.ExecuteMainMethod
, поскольку эта часть стека была перезаписана при обработке исключения и отладчике.
К сожалению, любой анализ этой трассировки стека бесполезен для ваших целей, потому что она была захвачена слишком поздно. То, что мы видим, это исключение, возникающее во время обработки WM_LBUTTONDOWN, который преобразуется в WM_SYSCOMMAND, который затем перехватывает исключение. Другими словами, вы нажали на что-то, что вызвало системную команду (например, изменение размера), что вызвало исключение. В тот момент, когда эта трассировка стека была захвачена, исключение уже обрабатывалось. Причина, по которой вы видите вызовы User32 и UxTheme, заключается в том, что они связаны с обработкой нажатия кнопки. Они не имеют ничего общего с настоящей проблемой.
Вы находитесь на правильном пути, но вам нужно будет захватить трассировку стека в момент сбоя выделения (или вы можете использовать один из других подходов, которые я предложил выше).
Вы будете знать, что у вас есть правильная трассировка стека, когда все управляемые кадры в вашей первой трассировке стека появляются в ней, а вершина стека является ошибочным выделением памяти. Обратите внимание, что нас действительно интересуют только неуправляемые фреймы, которые появляются над вызовом DUCE+Channel.SyncFlush
- все, что ниже, будет NET Framework и код вашего приложения.
Как получить собственную трассировку стека в нужное время
Вы хотите получить трассировку стека во время первого сбоя выделения памяти в показанном вызове DUCE+Channel.SyncFlush
. Это может быть сложно. Я использую три подхода: (обратите внимание, что в каждом случае вы начинаете с точки останова внутри вызова SyncFlush - см. Примечание ниже для более подробной информации)
Установите отладчик так, чтобы он прерывался на все исключения (управляемые и неуправляемые), затем продолжайте нажимать go (F5 или «g») до тех пор, пока он не сломает интересующее вас исключение выделения памяти. Это первое, что нужно сделать. попробовать, потому что это быстро, но часто не работает при работе с собственным кодом, потому что собственный код часто возвращает код ошибки вызывающему собственному коду вместо генерирования исключения.
Настройте отладчик так, чтобы он прерывался на все исключения, а также устанавливайте точки останова в общих процедурах выделения памяти, затем нажимайте F5 (go) до тех пор, пока не произойдет исключение, считая, сколько F5 вы нажали. В следующий раз, когда вы запустите, используйте на F5 меньше, и вы можете быть на вызове распределения, который сгенерировал исключение. Захватите стек вызовов в Блокнот, затем F10 (шаг за шагом) несколько раз оттуда, чтобы увидеть, действительно ли было выделение, которое не удалось.
Установите точку останова на первом собственном кадре, который вызывается SyncFlush (это wpfgfx_v0300! MilComposition_SyncFlush), чтобы пропустить переход от управляемого к собственному, а затем F5 для запуска к нему. F10 (шаг за шагом) через функцию it, пока 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 разумны, значит, ошибка в драйвере видеокарты.