Работа с чрезвычайно малыми приращениями времени

Хорошо, это название, возможно, было расплывчатым, но позвольте мне объяснить.

Я имею дело с большим списком из сотен сообщений, которые должны быть отправлены на шину CAN в виде массивов байтов. Каждое из этих сообщений имеет свойство Interval, указывающее, как часто должно отправляться сообщение в миллисекундах. Но я вернусь к этому.

Итак, у меня есть нить. Поток проходит через этот гигантский список сообщений до тех пор, пока не остановится, с телом примерно таким:

Stopwatch timer = new Stopwatch();
sw.Start();
while(!ShouldStop)
{
   foreach(Message msg in list)
   {
      if(msg.IsReadyToSend(timer)) msg.Send();
   }
}

Это прекрасно работает, с феноменальной точностью соблюдает интервал объектов Message. Тем не менее, он загружает весь процессор. Проблема в том, что из-за огромного количества сообщений и особенностей CAN-шины обычно проходит менее половины миллисекунды, прежде чем поток должен отправить другое сообщение. Никогда не было бы случая, чтобы поток мог спать, скажем, более 15 миллисекунд.

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

РЕДАКТИРОВАТЬ: Возможно, стоит упомянуть, что свойство Interval Message не является абсолютным. Пока поток продолжает извергать сообщения, получатель должен быть счастлив, но если поток регулярно засыпает, скажем, на 25 мс из-за того, что потоки с более высоким приоритетом крадут его квант времени, это может вызвать тревогу у получателя.


person user2008803    schedule 01.02.2016    source источник
comment
Просто добавьте Thread.Sleep(0) прямо внутри while(), чтобы он играл красиво. Если это приложение с графическим интерфейсом, то ваш while() не должен находиться в основном потоке.   -  person MickyD    schedule 01.02.2016
comment
@Micky Sleep (0) освободит оставшуюся часть временного интервала - и, следовательно, поток, вероятно, будет запланирован для выполнения примерно через 7-10 мс (в среднем), поэтому отсутствует отправка до 20 сообщений (менее чем за полмиллисекунды до .. . отправьте еще одно сообщение)   -  person Alexei Levenkov    schedule 01.02.2016
comment
@AlexeiLevenkov звучит так, будто пришло время вырваться из Arduino и коммутационных плат! xkcd.com/730   -  person Aron    schedule 01.02.2016
comment
Основываясь на обновленной публикации, обычный Sleep(0) может быть приемлемым. Пытаться.   -  person Alexei Levenkov    schedule 01.02.2016
comment
Да, поразмыслив, я думаю, что готов расстаться с несколькими миллисекундами потерянной точности. Sleep(0) должен решить мою проблему.   -  person user2008803    schedule 01.02.2016
comment
@AlexeiLevenkov Я не знал, что это так долго, спасибо, приятель.   -  person MickyD    schedule 01.02.2016


Ответы (2)


Как вы уже обнаружили, самый точный способ «ожидания» процессора — это опрос RTC. Однако это требует больших вычислительных ресурсов. Если вам нужно добиться точности часов во времени, другого пути нет.

Однако в своем исходном сообщении вы сказали, что время составляет порядка 15 мс.

На моем четырехъядерном i5 с частотой 3,3 ГГц дома 15 мс x 3,3 ГГц = 50 миллионов тактовых циклов (или 200 миллионов, если считать все ядра).

Это вечность.

Свободное время сна, скорее всего, более чем достаточно точно для ваших целей.

Откровенно говоря, если вам нужен Hard RT, C# на виртуальной машине .net, работающей на .net GC в ядре Windows, — неправильный выбор.

person Aron    schedule 01.02.2016
comment
Итак, вы бы предложили простой Thread.Sleep(0) для каждого цикла моего потока или, по крайней мере, когда я просматриваю весь список сообщений и ничего не нужно отправлять? РЕДАКТИРОВАТЬ: Кроме того, время между циклами потока больше похоже на 0,25 миллисекунды. Я хотел сказать, что поток никогда не будет бездействовать в течение 15 миллисекунд. Блин, 5 маловероятно. - person user2008803; 01.02.2016
comment
@user2008803 user2008803 После прочтения о Quanta, да. Но это не гарантия корректной работы. Мой реальный совет состоял бы в том, чтобы загрузить работу на что-то, что является ЖЕСТКИМ RT, например, Arduino, и закодировать реальный интерфейс CAN на языке RT, таком как C ++. - person Aron; 01.02.2016
comment
Я думаю, мне придется называть это достаточно близко. Потеря точности здесь не является решающим фактором. Этот код не предназначен для того, чтобы быть настоящим интерфейсом CAN, это больше похоже на фальшивую коробку для тестирования клиентского кода путем отправки тех же сообщений, которые сервер отправил бы в настоящей рабочей среде. - person user2008803; 01.02.2016

Основываясь на обновленных требованиях, очень вероятно, что настройки по умолчанию с Sleep(0) может быть достаточно — сообщения могут отправляться небольшими пакетами, но это звучит нормально. Использование мультимедийного таймера может сделать вспышку менее заметной. Повышение терпимости к получателю сообщений может быть лучшим подходом (если это возможно).


Если вам нужна точность в миллисекундах с хорошими гарантиями - C # для Windows не лучший выбор - может потребоваться отдельное оборудование (даже Adruino) или, по крайней мере, код более низкого уровня, чем C #.

Windows не является ОС RT, поэтому вы не можете получить точность менее миллисекунды.

Занятая петля (возможно, в потоке с высоким уровнем приоритета), как у вас, является распространенным подходом, если вам нужна точность менее миллисекунды.

Вы можете попробовать использовать таймеры мультимедиа (пример — Multimedia прерывания таймера в C# (первые два прерывания неверны)), а также изменить интервал времени по умолчанию на 1 мс (см. Почему таймеры .NET ограничены разрешением 15 мс? для примера/пояснения).

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

Примечание: вам, очевидно, следует подумать, подходит ли более разумная структура данных (например, куча или приоритетная очередь могут работать лучше, чтобы найти следующий элемент).

person Alexei Levenkov    schedule 01.02.2016
comment
Переустройство структуры данных, безусловно, входит в мой список «необходимых дел», но я хотел сначала решить текущую проблему, поскольку гораздо больше циклов процессора тратится впустую просто на вращение, чем на вычисление необходимости отправки сообщения. В любом случае, я рассмотрю приоритетную очередь. - person user2008803; 01.02.2016