DispatcherTimer и пользовательский интерфейс обновляют ограничения в C # silverlight - PullRequest
8 голосов
/ 12 января 2011

Снова прошу прощения за вопрос, который может быть простым для всех вас.У меня ограниченное понимание того, что происходит за кулисами в Silverlight.

У меня есть приложение для построения диаграмм (Visiblox), которое я использую в качестве скользящей области, обновляемой каждые 20 мс, добавляя и удаляя точку.В псевдокоде:

List<Point> datapoints= new List<Point>();
Series series = new Series(datapoints);
void timer_tick(){
  datapoints.Add(new Point);
  datapoints.RemoveAt(0);
  // no need to refresh chart, it does refresh automatically
}

При запуске 6 серий в этом инструменте построения диаграмм он начал показывать немного вяло.Изменение тика на 10 мс не имело никакого значения, график обновлялся с той же скоростью, поэтому кажется, что 20 мс - это ограничение скорости (пользовательский интерфейс или график?).

Я пробовал с CompositionTarget.Rendering и получил то же самоерезультаты: ниже 20 мс не было никакой разницы в скорости.

Тогда я случайно включил оба, и скорость удвоилась.Итак, я протестировал несколько потоков (2, 3, 4), а скорость удвоилась, утроилась и увеличилась в четыре раза.У него еще нет блокировок, так как я даже не знаю, какой процесс мне нужен, чтобы генерировать блокировку, но у меня нет повреждений данных или утечек памяти.

У меня есть вопрос, почему вялая диаграмма на 20 мс можетне работает на 10 мс, но смехотворно быстро, когда многопоточный?Процесс обновления пользовательского интерфейса выполняется быстрее?Вычисляется ли вдвое больше карт?Или есть ли ограничение на скорость выполнения одного DispatcherTimer?

Спасибо!


Редактировать: у меня есть опыт встроенного кодирования, поэтому, когда я думаю о потоках и таймингах, я сразу же думаю о переключении булавки в аппаратном обеспечении и подключении области действиядля измерения длины процесса.Я новичок в потоках в C # и нет контактов для подключения областей.Есть ли способ графически увидеть тайминги потоков?

Ответы [ 2 ]

7 голосов
/ 12 января 2011

DispatcherTimer, который запускает событие Tick в потоке пользовательского интерфейса, - это то, что считается таймером с низким разрешением или с низкой точностью, поскольку его интервал фактически означает «тик не раньше x с момента последнего тика». Если поток пользовательского интерфейса занят чем-либо (обработка ввода, обновление графика и т. Д.), То это приведет к задержке событий таймера. Кроме того, наличие большого числа элементов DispatcherTimer, тикающих в потоке пользовательского интерфейса с очень малыми интервалами, также замедлит скорость отклика вашего приложения, поскольку во время вызова события Tick приложение не может отвечать на ввод.

Итак, как вы заметили, для частой обработки данных вы должны перейти в фоновый поток. Но есть предостережения. Тот факт, что вы сейчас не наблюдаете коррупцию или другие ошибки, может быть чисто случайным. Если список изменяется в фоновом потоке в то время, когда поток переднего плана пытается прочитать из него, вы в конечном итоге потерпите крах (если вам повезет) или увидите поврежденные данные.

В вашем примере у вас есть комментарий, который гласит: «Нет необходимости обновлять график, он обновляется автоматически». Это заставляет меня задуматься, откуда диаграмма узнает, что вы изменили коллекцию datapoints? List<T> не вызывает события при его изменении. Если бы вы использовали ObservableCollection<T>, я бы отметил, что каждый раз, когда вы удаляете / добавляете точку, вы потенциально обновляете график, что может замедлять процесс.

Но если вы на самом деле используете List<T>, тогда должно быть что-то еще (возможно, другой таймер?), Который обновляет график. Может быть, сам элемент управления диаграммы имеет встроенный механизм автообновления?

В любом случае, проблема немного сложная , но не совсем новая . Есть способы, которыми вы могли бы поддерживать коллекцию в фоновом потоке и связываться с ней из потока пользовательского интерфейса. Но чем быстрее обновляется ваш пользовательский интерфейс, тем больше вероятность того, что фоновый поток освободит блокировку.

Один из способов минимизировать это - использовать LinkedList<T> вместо List<T>. В конец LinkedList добавляется O (1), поэтому удаляется элемент. List<T> необходимо сдвинуть все на единицу, когда вы удаляете элемент с самого начала. Используя LinkedList, вы можете заблокировать его в фоновом потоке (ах) и минимизировать время удержания блокировки. В потоке пользовательского интерфейса вам также необходимо получить такую ​​же блокировку и либо скопировать список в массив, либо обновить диаграмму, пока блокировка удерживается.

Другим возможным решением будет буферизовать «порции» точек в фоновом потоке и опубликовать пакет из них в потоке пользовательского интерфейса с помощью Dispatcher.BeginInvoke, где вы сможете безопасно обновить коллекцию.

4 голосов
/ 14 января 2011

Ключевым моментом здесь, я думаю, является осознание того, что Silverlight рендерится с максимальной частотой кадров 60 кадров в секунду по умолчанию (настраивается через свойство MaxFrameRate).Это означает, что тики DispatcherTimer будут срабатывать не более 60 раз в секунду.Кроме того, вся работа рендеринга происходит также в потоке пользовательского интерфейса, поэтому DispatcherTimer запускается с той скоростью, с которой в лучшем случае происходит рисование, на что указывал предыдущий плакат.

Результат того, что вы делаетедобавление трех таймеров - это просто запуск метода «добавить данные» 3 раза за цикл обработки событий, а не один раз, так что ваши диаграммы будут выглядеть намного быстрее, но на самом деле частота кадров примерно одинакова.Вы можете получить тот же эффект с одним DispatcherTimer и просто добавить в 3 раза больше данных на каждый тик.Вы можете убедиться в этом, подключившись к событию CompositionTarget.Rendering и посчитав там частоту кадров параллельно.

Созданная ранее точка ObservableCollection является хорошей, но в Visiblox есть немногоВолшебство, чтобы попытаться смягчить последствия этого, поэтому, если вы добавляете данные с очень высокой скоростью, обновления диаграммы будут сгруппированы со скоростью цикла рендеринга, а ненужные повторные рендеры будут отброшены.

Что касается вашего мнения о том, что вы привязаны к реализации ObservableCollection IDataSeries, вы можете совершенно свободно реализовать интерфейс IDataSeries самостоятельно, например, поддерживая его простым List.Просто имейте в виду, что, очевидно, если вы сделаете это, диаграмма больше не будет автоматически обновляться при изменении данных.Вы можете принудительно обновить диаграмму, вызвав Chart.Invalidate () или изменив вручную заданный диапазон осей.

...