Comments 29
Интересный вариант. Теперь хорошо бы придумать автоматический способ обнаруживать такой блокирующий код, чтобы можно было его зарефакторить.
Можно попробовать Debugger.Launch, но вопрос что будет в стеке что делать дальше.
Наверное, задачу останова будет попроще решить.
Ну что вы. Я же не прошу точно указать причину ошибки. Достаточно было бы обнаружить факт блокировки и получить callstack этого потока, чтобы отправить его в качестве багрепорта дальше уже в ручном режиме разобраться. Без этого приведенный в статье способ выглядит как заглушка — «мы знаем что проблема может быть, но локализовать и исправить не можем, так что пока хоть так».
Отличный пост, приму на заметку.
Небольшой комментарий не въедливости ради, а для того, чтобы у тех, кто будет использовать этот код, не произошёл «нежданчик»:
Небольшой комментарий не въедливости ради, а для того, чтобы у тех, кто будет использовать этот код, не произошёл «нежданчик»:
Скрытый текст
Эта строка:
и вторая для UIReleased — нет ни проверки на null, ни первичного переноса значения делегата в отдельную переменную (что настоятельно рекомендуется, тем более, что в этом месте гарантирована многопоточность).
Dispatcher.CurrentDispatcher.Invoke(() => UIBlocked());
и вторая для UIReleased — нет ни проверки на null, ни первичного переноса значения делегата в отдельную переменную (что настоятельно рекомендуется, тем более, что в этом месте гарантирована многопоточность).
Ну ещё можно посоветовать заменить
на
и
на
Всё-таки мерить время через DateTime.Now как-то не очень прилично
_lastForegroundTimerTickTime = DateTime.Now;
на
_stopwatch = Stopwatch.StartNew();
и
var totalMilliseconds = (DateTime.Now - _lastForegroundTimerTickTime).TotalMilliseconds;
на
var totalMilliseconds = _stopwatch.ElapsedMilliseconds;
Всё-таки мерить время через DateTime.Now как-то не очень прилично
Так это бенчмарки меряются
Не вижу проблемы измерять сотни миллисекунд через
Stopwatch
'ем.Не вижу проблемы измерять сотни миллисекунд через
DateTime
.Слепые тоже не видят. Но обычно на форумах этим не гордятся.
Погрешность может быть очень большой. На моей памяти правда погрешность была в пределах 16мс, но я не писал под 95 винду. Там это было 55мс.
И самое главное «как долго заняла операция» и «сколько сейчас времени» — это разные задачи. Средства очевидно тоже разные.
Погрешность может быть очень большой. На моей памяти правда погрешность была в пределах 16мс, но я не писал под 95 винду. Там это было 55мс.
И самое главное «как долго заняла операция» и «сколько сейчас времени» — это разные задачи. Средства очевидно тоже разные.
Ну, очевидно, погрешность в 16мс поломает всю задачу определения, повис ли UI-поток.
Осталось выставить CPU Affinity, чтобы поток не скакал между ядрами, и прогреть кэш.
Осталось выставить CPU Affinity, чтобы поток не скакал между ядрами, и прогреть кэш.
То есть человек меняющий константу FreezeTimeLimit должен помнить что если он ее уменьшит до некого N при котором погрешность будет составлять критические для тов. withkittens скажем 10% — ему нужно будет заменить семантически менее корректное решение на семантически более корректное, потому что когда писали изначально — погрешность всех устраивала. Так?
Мне удивительно, как вы ратуете за
Stopwatch
с наносекундной точностью, когда как DispatcherTimer
, использующийся в статье, имеет погрешность в 10мс минимум.А если пользователь изменит системное время?
У вас может перевод часов на зимнее время и обратно случиться. Или сработать синхронизация времени по NTP. Или пользователь что-то поменяет. DateTime.Now подходит для ответа на вопрос, какое сейчас системное время, но не подходит для ответа на вопрос, сколько прошло времени с заданного момента в прошлом. Для этого используется монотонный счётчик времени, до которого на винде можно достучаться через GetTickCount64 и QueryPerformanceCounter. Второй используется в Stopwatch и использует HPET, но затратнее. Первый даёт меньшую точность, менее затратен, но нужно делать P/Invoke.
См.
github.com/akkadotnet/akka.net/issues/846
github.com/akkadotnet/akka.net/tree/dev/src/core/Akka/Util/MonotonicClock.cs
См.
github.com/akkadotnet/akka.net/issues/846
github.com/akkadotnet/akka.net/tree/dev/src/core/Akka/Util/MonotonicClock.cs
Dispatcher.CurrentDispatcher.Invoke(() => UIBlocked());
Я правильно понимаю, что BackgroundTimerTick вызывается на потоке ThreadPool-а, и вызов в нём Dispatcher.CurrentDispatcher порождает новый диспетчер, связанный с этим потоком? На мой взгляд, это довольно странно. Задачи, выполняемые на потоках пула, не должны влиять на состояние потоков, в которых они запускаются.
Постоянно дергать DateTime.Now плохая идея. Это крайне медленная функция. Для таких задач лучше подходит DateTime.UtcNow
(del)
Могу рассказать о настоящей серебряной пуле для детекта UI Freeze: есть в Windows Vista+ механизм ETW(Event Tracing for Windows), и готовый провайдер, который умеет кидать сообщения и коллстек, когда какое-нибудь приложение(не обязательно WPF и .NET) в системе не опрашивает очередь сообщений более 200ms. Не нужно лезть в код и инструментировать, создавать два диспетчера. Всё работает в режиме Attach.
С помощью этого механизма dotTrace в режиме Timeline показывает вам те самые UI Freeze на графике и можно поизучать хотспоты на этих участках.
С помощью этого механизма dotTrace в режиме Timeline показывает вам те самые UI Freeze на графике и можно поизучать хотспоты на этих участках.
Обнаружить, что UI «отвис» можно подпиской на событе Dispatcher.Hooks.DispatcherInactive
Думаю обнаружить начало выполнения операций диспетчером можно с использованием других событий Dispatcher.Hooks.
Думаю обнаружить начало выполнения операций диспетчером можно с использованием других событий Dispatcher.Hooks.
WPF разрешает создавать несколько UI тредов. Делается это так:
Указанным способом создается несколько блокированных инстансов потока, которые работают синхронно.
Если кому-то понадобится создать несколько реальных UI потоков WPF, то можно подглядеть решение на MSDN.
Я использовал такое решение для создания на WPF индикатора длительных операций, способный крутить анимацию даже при замораживании основного потока UI. ОСТОРОЖНО!!! Корректная реализация IDisposable требуется.
Нет, указанный автором способ создает реальные потоки, ограничение тут в другом. Если делать как автор — то можно создать в отдельном потоке только отдельное окно. По вашей же ссылке предлагается создать контрол, лежащий где-то в дереве — но при этом работающий в своем потоке.
Sign up to leave a comment.
Детектор блокировок UI в WPF c нотификацией