2011-01-12 3 views
8

Снова прошу прощения за вопрос, который может быть прост для всех вас. У меня есть ограниченное понимание того, что происходит за кулисами в Silverlight.DispatcherTimer и ограничения пользовательского интерфейса в C# 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#, и нет контактов для подключения областей. Есть ли способ увидеть графики потоков графики графически?

ответ

4

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

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

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

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

+0

Вы правы. Это иллюзия. На медленных скоростях «перекатывание» диаграммы легче отслеживать глазами, а медлительность легко обнаруживается, но, обновляя более чем одну точку за раз, медлительность трудно увидеть. Благодаря! – PaulG

7

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

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

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

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

В любом случае проблема немного сложная but not completely new. Существуют способы, которыми вы могли бы поддерживать коллекцию в фоновом потоке и связываться с ней из потока пользовательского интерфейса. Но чем быстрее обновится пользовательский интерфейс, тем более вероятно, что вы будете ожидать, что фоновый поток освободит блокировку.

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

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

+0

Спасибо за великолепное понимание. Чтобы ответить на некоторые из ваших проблем, я не совсем использую List <>, но представляет собой реализацию коллекции, в которой есть INotifyCollectionChanged. Поскольку это стороннее приложение, я не могу с этим поделать. Мой первый подход к этому был именно таким, каким вы предложили, я рисовал растровое изображение в фоновом режиме, пока я прокручивал растровое изображение на переднем плане. Но из-за нехватки времени и качества моего «домашнего» инструмента построения диаграмм я выбрал сторонний. Так что LinkedList и другие не работают, мне нужно использовать их конкретную реализацию ... – PaulG

+0

(продолжение, закончились символами) Другое дело, я просто понял, что System.Windows.Threading создает потоки, которые работают в том же потоке, что и пользовательский интерфейс, в противном случае было бы невозможно обновить пользовательский интерфейс. Правильно? Поэтому тот факт, что я думал, что я многопоточность, должен быть неправильным. Что происходит? Предоставляет ли Silverlight временные интервалы для совместного использования между пользовательским интерфейсом и DispatchTimer и тот факт, что я создал несколько из них, увеличивает приоритет задач таймера? – PaulG

+0

(продолжение III) Вот почему я думаю, что я не вижу сбоев или ошибок, потому что на самом деле я не выполняю несколько потоков, но только один за другим.Мое приложение - это инструмент моделирования, который обрабатывает тысячи вычислений матрицы за период таймера, в результате чего миллионы вычислений в секунду. Я должен был потерпеть крах. Пожалуйста, поправьте меня, если я ошибаюсь. Это все предположения. – PaulG