2014-01-13 1 views
6

У меня есть поток для создания сетевого пакета каждые 40 мс (25 Гц), это бесконечный цикл (до тех пор, пока он не будет остановлен), и я использую thread.sleep.DateTime drifting - weird issue через 2 часа

Когда я создаю пакет, одним из значений является текущее время GPS, используя DateTime.UtcNow и добавляя секунды прыжка.

Это работает отлично, когда я начинаю, но он дрейфует со временем, примерно через 2 часа, он отстает на 5 секунд.

У меня есть Symmetrom GPS Time Server, и я использую их программное обеспечение как клиент NTP, и он говорит, что совокупный дрейф на ПК составляет около 1,2 секунды (большая часть из того, что я заметил, является дрейфом, а ПК выключен и не синхронизирован с NTP).

У кого-нибудь есть идеи, что происходит не так? Я знаю, что thread.sleep - это не идеальное время, и Windows - это не RTOS, но дрейф не имеет смысла, если отбросить кадры.

Я не могу отправить код из-за некоторые патентованные и ИТАР вопросов, но я могу опубликовать примерный план:

while(!abort) { 
    currentTime = DateTime.UtcNow + leapSeconds ; 
    buildPacket(currentTime); 
    stream.Write(msg, 0, sendSize); 
    //NetworkStream Thread.Sleep(40); 
} 

Я в Windows 7 и с помощью Visual Studios 2010.

+3

Вы используете 'Sleep (40)', а затем что-то делаете? Учтите, сколько времени требуется для сборки пакета? –

+0

Сообщите нам ваш код? –

+2

Я бы сказал, что 5 секунд не так уж плохо, учитывая, что у вас было более 180 тысяч тиков. Даже очень небольшая неточность в Thread.Sleep ([и это действительно не идеально) (http://stackoverflow.com/a/1303708/2316200)) приведет к большому интервалу с течением времени. Это еще хуже с Windows.Timers, где вы можете получить 10 секунд после примерно 30 минут. –

ответ

1

I подумайте, что это происходит, потому что время выполнения цикла while составляет 40 мс (ваш сон) + время, необходимое для выполнения кода, который создает пакет.

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

+2

А также потому, что нить не пробуждается в точности этой точной миллисекунде, которую вы хотите, но когда ОС дает ему шанс (и это зависит от множества факторов, таких как текущий счет процесса) –

0

Существует, вероятно, много накладных расходов, включая сетевое IO. Вы можете разъединить время от создания, как это:

public void Timing() 
{ 
    // while (true) to simplify... 
    // You should probably remember the last time sent and adjust the 40ms accordingly 
    while (true) 
    { 
     SendPacketAsync(DateTime.UtcNow); 
     Thread.Sleep(40); 
    } 
} 

public Task SendPacketAsync(DateTime timing) 
{ 
    return Task.Factory.StartNew(() => { 
     var packet = ...; // use timing 
     packet.Send(); // abstracted away, probably IO blocking 
    }); 
} 
0

Других ответов на проблемы ... У вас есть накладные расходы на вершине того, что вы спите.

TPL

Если вы действуете в мире TPL, то вы можете создать довольно простое решение:

while(running) 
    await Task.WhenAll(Task.Delay(40), Task.Run(()=>DoIO())); 

Это отличное решение, потому что он будет ждать операции ввода-вывода (DoIO()) в случае, если требуется более 40 мс. Это также позволяет избежать с помощью Thread.Sleep(), которая всегда идеально ...

Таймер

Так вместо этого, используйте таймер (System.Threading.Timer), который будет срабатывать каждые 40ms. Таким образом, вы можете создавать и отправлять пакет, но таймер также по-прежнему рассчитывает. Риск здесь заключается в том, что если операция ввода-вывода занимает более 40 мс, у вас есть состояние гонки.

ПРИМЕЧАНИЕ

40ms является OK время, чтобы ожидать точного обратного вызова, ОДНАКО, позволяет сказать, что вы решили, что вам нужно 4 мсек, то ОС, вероятно, не сможет обеспечить такого рода разрешение. Для такой точности вам понадобится ОС реального времени.

0

Я бы предположил, что вас укусили две вещи.

  • По умолчанию разрешение по умолчанию в современных Windows составляет 10 мс (хотя об этом нет никаких гарантий). Если вы оставите это, у вас будет, в лучшем случае, много дрожания. Вы можете использовать мультимедийный API для увеличения разрешения таймера. Поиск MSDN для timeGetDevCaps, timeBeginPeriod и timeEndPeriod.
  • Вы должны рассчитать свой интервал ожидания на основании некоторого фиксированного времени начала, а не спать 40 мс на каждой итерации. Приведенный ниже код показывает это.

Вот некоторые скелетные код:

static void MyFunction() 
{ 
    // 
    // Use timeGetDevCaps and timeBeginPeriod to set the system timer 
    // resolution as close to 1 ms as it will let you. 
    // 

    var nextTime = DateTime.UtcNow; 

    while (!abort) 
    { 
     // Send your message, preferably do it asynchronously. 

     nextTime = nextTime.AddMilliseconds(40); 
     var sleepInterval = nextTime - DateTime.UtcNow; 

     // may want to check to make sure sleepInterval is positive. 

     Thread.Sleep(sleepInterval); 
    } 

    // 
    // Use timeEndPeriod to restore system timer resolution. 
    // 
} 

Я не знаю ни оберток .Net для мультимедийных функций времени * API. Возможно, вам понадобится использовать PInvoke для вызова их из C#.