Как запустить функцию C # в определенное время с точностью до миллисекунды? - PullRequest
3 голосов
/ 15 июля 2011

У меня есть 2 компьютера, время которых синхронизируется через NTP, который гарантирует, что время будет отличаться только на несколько миллисекунд.Один из компьютеров отправит сообщение через TCP на другой, чтобы запустить определенную функцию c # в определенное время в будущем на обоих компьютерах.

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

ОБНОВЛЕНИЕ:

DateTime.Now не может использоватьсяв решении, поскольку оно имеет низкое разрешение.

Кажется, что Thread.Sleep () может быть принудительно иметь разрешение в 1 мс, импортируя:

[DllImport("winmm.dll", EntryPoint="timeBeginPeriod")]
public static extern uint MM_BeginPeriod(uint uMilliseconds);

и используя:

MM_BeginPeriod(1);

Чтобы вернуться к предыдущему разрешению, импортируйте:

[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
public static extern uint MM_EndPeriod(uint uMilliseconds);

и используйте:

MM_EndPeriod(1);

ОБНОВЛЕНИЕ 2:

Iпротестировал Thread.Sleep () со многими значениями, и, похоже, что в среднем он будет стремиться к указанному промежутку времени.

Вызов Thread.Sleep () только один раз обычно остается около половины мс вокруг целевого значенияпромежуток времени, поэтому он довольно точен с точностью до миллисекунды.

Использование методов winmm.dll timeBeginPeriod и timeEndPeriod, похоже, не влияют на точность результата.

РЕШЕНИЕ:

Одним из способов будет использование timeSetEvent (устарело) или CreateTimerQueueTimer.

В настоящее время проблема заключается в том, что обеим параметрам в качестве параметра требуется время, оставшееся до срабатывания функции, а не время, в которое она должна срабатывать.Таким образом, задержка до желаемого времени для триггера должна быть рассчитана, но DateTime.Now предлагает низкое разрешение.Я нашел класс, который позволяет получать текущее DateTime в высоком разрешении.Таким образом, теперь оставшееся время можно рассчитать с высоким разрешением и передать в качестве параметра CreateTimerQueueTimer.

Ответы [ 6 ]

3 голосов
/ 15 июля 2011

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

    public delegate void TimerEventHandler(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);

    /// <summary>
    /// A multi media timer with millisecond precision
    /// </summary>
    /// <param name="msDelay">One event every msDelay milliseconds</param>
    /// <param name="msResolution">Timer precision indication (lower value is more precise but resource unfriendly)</param>
    /// <param name="handler">delegate to start</param>
    /// <param name="userCtx">callBack data </param>
    /// <param name="eventType">one event or multiple events</param>
    /// <remarks>Dont forget to call timeKillEvent!</remarks>
    /// <returns>0 on failure or any other value as a timer id to use for timeKillEvent</returns>
    [DllImport("winmm.dll", SetLastError = true,EntryPoint="timeSetEvent")]
    static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution, TimerEventHandler handler, ref UInt32 userCtx, UInt32 eventType);

    /// <summary>
    /// The multi media timer stop function
    /// </summary>
    /// <param name="uTimerID">timer id from timeSetEvent</param>
    /// <remarks>This function stops the timer</remarks>
    [DllImport("winmm.dll", SetLastError = true)]
    static extern void timeKillEvent(  UInt32 uTimerID );

    TimerEventHandler tim = new TimerEventHandler(this.Link);
    public void Link(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2)
    {
        _counter++;
        if( (_counter % 10 ) == 0)
            setLblTxt();
    }
1 голос
/ 15 июля 2011

Есть статья Питера А. Бромберга о Высокоточной синхронизации кода в .NET .Опрос DateTime не будет работать, потому что DateTime.Now имеет относительно низкое разрешение (около 16 мс).

1 голос
/ 15 июля 2011

.NET 4 поставляется со встроенным планированием задач для параллельной обработки.

Вы могли бы написать что-то вроде этого:

void QueueIt(long tick)
{
    Task workTask = new Task(() => MyMethod());

    Task scheduleTask = Task.Factory.StartNew( () =>
    {                
        WaitUtil(tick); // Active waiting
        workTask.Start();
    });
}


void WaitUntil(long tick)
{
    var toWait = tick - DateTime.Now.Ticks;
    System.Threading.Thread.Sleep(toWait);
}
0 голосов
/ 15 июля 2011

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

0 голосов
/ 15 июля 2011

А как же:

public void ReceivesIncomingTCP(DateTime startDate)
{
Thread.Sleep(startDate.Subtract(DateTime.Now).Milliseconds)
DoAction();
}
0 голосов
/ 15 июля 2011

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

Однако я не знаю, каковы гарантии точности для таймеров Windows - вам следует проверить, достаточно ли этого для ваших нужд (и помнить, что он может варьироваться от одной машины к другой).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...