Таймер высокого разрешения - PullRequest
13 голосов
/ 21 августа 2011

Я хочу иметь таймер с разрешением около 5 миллисекунд.но текущий таймер в .Net имеет разрешение около 50 мс.Я не смог найти ни одного рабочего решения, которое создает таймер высокого разрешения, хотя некоторые утверждают, что вы можете сделать это в C #.

Ответы [ 8 ]

15 голосов
/ 21 августа 2011

В отношении информации, которую ОП конкретно спрашивал о классе Timer, который генерирует события через регулярные промежутки времени. Я изменил этот ответ, мой старый ответ ниже горизонтального правила.

Я протестировал следующий код с классом Timer, и кажется, что он может достичь по крайней мере в диапазоне 14 - 15 миллисекунд на моей машине. Попробуйте сами и посмотрите, сможете ли вы воспроизвести это. Таким образом, время отклика менее 50 миллисекунд возможно, но оно не может быть равным ровно одной миллисекунде.

using System;
using System.Timers;
using System.Diagnostics;

public static class Test
{
    public static void Main(String[] args)
    {
        Timer timer = new Timer();
        timer.Interval = 1;
        timer.Enabled = true;

        Stopwatch sw = Stopwatch.StartNew();
        long start = 0;
        long end = sw.ElapsedMilliseconds;

        timer.Elapsed += (o, e) =>
        {
            start = end;
            end = sw.ElapsedMilliseconds;
            Console.WriteLine("{0} milliseconds passed", end - start);
        };

        Console.ReadLine();
    }
}

NB. Ниже приведен мой старый ответ, когда я подумал, что ОП говорит о времени. Следующее является просто полезной информацией в отношении хронометража продолжительности событий, но не предоставляет никакого способа запуска событий через регулярные промежутки времени. Для этого необходим класс Timer.

Попробуйте использовать класс секундомера в System.Diagnostics: http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

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

int resolution = 1E9 / Stopwatch.Frequency;
Console.WriteLine("The minimum measurable time on this system is: {0} nanoseconds", resolution);

Если вы беспокоитесь о том, откуда это на самом деле, документация подразумевает, что она на самом деле внутренне вызывает функции Win32 более низкого уровня:

Класс Секундомер помогает манипулировать временем, связанным с счетчики производительности в управляемом коде. В частности, частота поле и метод GetTimestamp можно использовать вместо неуправляемого Win32 APIs QueryPerformanceFrequency и QueryPerformanceCounter.

11 голосов
/ 21 августа 2011

А как же этот один?

public class HiResTimer
{
    private bool isPerfCounterSupported = false;
    private Int64 frequency = 0;

    // Windows CE native library with QueryPerformanceCounter().
    private const string lib = "coredll.dll";
    [DllImport(lib)]
    private static extern int QueryPerformanceCounter(ref Int64 count);
    [DllImport(lib)]
    private static extern int QueryPerformanceFrequency(ref Int64 frequency);

    public HiResTimer()
    {
        // Query the high-resolution timer only if it is supported.
        // A returned frequency of 1000 typically indicates that it is not
        // supported and is emulated by the OS using the same value that is
        // returned by Environment.TickCount.
        // A return value of 0 indicates that the performance counter is
        // not supported.
        int returnVal = QueryPerformanceFrequency(ref frequency);

        if (returnVal != 0 && frequency != 1000)
        {
            // The performance counter is supported.
            isPerfCounterSupported = true;
        }
        else
        {
            // The performance counter is not supported. Use
            // Environment.TickCount instead.
            frequency = 1000;
        }
    }

    public Int64 Frequency
    {
        get
        {
            return frequency;
        }
    }

    public Int64 Value
    {
        get
        {
            Int64 tickCount = 0;

            if (isPerfCounterSupported)
            {
                // Get the value here if the counter is supported.
                QueryPerformanceCounter(ref tickCount);
                return tickCount;
            }
            else
            {
                // Otherwise, use Environment.TickCount.
                return (Int64)Environment.TickCount;
            }
        }
    }

    static void Main()
    {
        HiResTimer timer = new HiResTimer();

        // This example shows how to use the high-resolution counter to 
        // time an operation. 

        // Get counter value before the operation starts.
        Int64 counterAtStart = timer.Value;

        // Perform an operation that takes a measureable amount of time.
        for (int count = 0; count < 10000; count++)
        {
            count++;
            count--;
        }

        // Get counter value when the operation ends.
        Int64 counterAtEnd = timer.Value;

        // Get time elapsed in tenths of a millisecond.
        Int64 timeElapsedInTicks = counterAtEnd - counterAtStart;
        Int64 timeElapseInTenthsOfMilliseconds =
            (timeElapsedInTicks * 10000) / timer.Frequency;

        MessageBox.Show("Time Spent in operation (tenths of ms) "
                       + timeElapseInTenthsOfMilliseconds +
                       "\nCounter Value At Start: " + counterAtStart +
                       "\nCounter Value At End : " + counterAtEnd +
                       "\nCounter Frequency : " + timer.Frequency);
    }
}
8 голосов
/ 22 августа 2011

Я нашел решение этой проблемы в следующем блоге: http://web.archive.org/web/20110910100053/http://www.indigo79.net/archives/27#comment-255

В нем рассказывается, как использовать мультимедийный таймер, чтобы иметь таймер с высокой частотой. У меня это работает нормально !!!

4 голосов
/ 14 июля 2017

Вот реализация, основанная на таймере StopWatch
https://gist.github.com/DraTeots/436019368d32007284f8a12f1ba0f545

  1. Работает на всех платформах и отличается высокой точностью везде StopWatch.IsHighPrecision == true

  2. Его Elapsed событие гарантированно не перекрывается (что может быть важно знать, поскольку изменения состояния внутри обработчика событий могут остаться незащищенными от многопоточного доступа)

Вот как это использовать:

Console.WriteLine($"IsHighResolution = {HighResolutionTimer.IsHighResolution}");
Console.WriteLine($"Tick time length = {HighResolutionTimer.TickLength} [ms]");

var timer = new HighResolutionTimer(0.5f);

// UseHighPriorityThread = true, sets the execution thread 
// to ThreadPriority.Highest.  It doesn't provide any precision gain
// in most of the cases and may do things worse for other threads. 
// It is suggested to do some studies before leaving it true
timer.UseHighPriorityThread = false;

timer.Elapsed += (s, e) => { /*... e.Delay*/ }; // The call back with real delay info
timer.Start();  
timer.Stop();    // by default Stop waits for thread.Join()
                 // which, if called not from Elapsed subscribers,
                 // would mean that all Elapsed subscribers
                 // are finished when the Stop function exits 
timer.Stop(joinThread:false)   // Use if you don't care and don't want to wait

Вот эталонный тест (и живой пример):
https://gist.github.com/DraTeots/5f454968ae84122b526651ad2d6ef2a3

Результаты установки таймера на 0,5 мс в Windows 10: enter image description here

Также стоит упомянуть, что:

  1. У меня была такая же точность на моно в Ubuntu.

  2. При игре с эталоном максимальное и очень редкое отклонение, которое я видел, составляло около 0,5 мс (что, вероятно, ничего не значит, это не системы реального времени, но все же стоит упомянуть)

  3. Тики секундомера не являются тиками TimeSpan. На этой машине с Windows 10 HighResolutionTimer.TickLength составляет 0,23 [нс].

  4. загрузка ЦП эталона составляет 10% для интервала 0,5 мс и 0,1% для интервала 200 мс

0 голосов
/ 14 апреля 2019

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

Справочная информация

Любые инструкции по задержке .NET будутвсегда сводитесь к разрешению системных часов, которое вы устанавливаете с помощью timeBeginPeriod ().Неважно, является ли это Thread.Sleep (N), Threading.Timer или Waitable.WaitOne (N).Тем не менее, временное разрешение DateTime.Now () и System.Diagnostic.Stopwatch намного выше, поэтому существует способ реализации событий точного времени, известных как hot loop .Горячие циклы все еще подвержены серьезной угрозе со стороны ОС, поскольку они, как правило, полностью занимают ядро ​​процессора.И вот что мы делаем, чтобы предотвратить это:

Передача кванта времени потока в горячем цикле другим потокам, когда в этом больше нет необходимости, вызывая Thread.Sleep (0) или .WaitOne (0)

Ниже приведен фрагмент кода, демонстрирующий простую реализацию планировщика высокого разрешения:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// High resolution scheduler. 
/// License: public domain (no restrictions or obligations)
/// Author: Vitaly Vinogradov
/// </summary>
public class HiResScheduler : IDisposable
{
    /// <summary>
    /// Scheduler would automatically downgrade itself to cold loop (Sleep(1)) when there are no
    /// tasks earlier than the treshold. 
    /// </summary>
    public const int HOT_LOOP_TRESHOLD_MS = 16;

    protected class Subscriber : IComparable<Subscriber>, IComparable
    {
        public Action Callback { get; set; }
        public double DelayMs { get; set; }

        public Subscriber(double delay, Action callback)
        {
            DelayMs = delay;
            Callback = callback;
        }

        public int CompareTo(Subscriber other)
        {
            return DelayMs.CompareTo(other.DelayMs);
        }

        public int CompareTo(object obj)
        {
            if (ReferenceEquals(obj, null))
                return -1;
            var other = obj as Subscriber;
            if (ReferenceEquals(other, null))
                return -1;
            return CompareTo(other);
        }
    }

    private Thread _spinner;
    private ManualResetEvent _allowed = new ManualResetEvent(false);
    private AutoResetEvent _wakeFromColdLoop = new AutoResetEvent(false);
    private bool _disposing = false;
    private bool _adding = false;

    private List<Subscriber> _subscribers = new List<Subscriber>();
    private List<Subscriber> _pendingSubscribers = new List<Subscriber>();

    public bool IsActive { get { return _allowed.WaitOne(0); } }

    public HiResScheduler()
    {
        _spinner = new Thread(DoSpin);
        _spinner.Start();
    }

    public void Start()
    {
        _allowed.Set();
    }

    public void Pause()
    {
        _allowed.Reset();
    }

    public void Enqueue(double delayMs, Action callback)
    {
        lock (_pendingSubscribers)
        {
            _pendingSubscribers.Add(new Subscriber(delayMs, callback));
            _adding = true;
            if (delayMs <= HOT_LOOP_TRESHOLD_MS * 2)
                _wakeFromColdLoop.Set();
        }
    }

    private void DoSpin(object obj)
    {
        var sw = new Stopwatch();
        sw.Start();
        var nextFire = null as Subscriber;
        while (!_disposing)
        {
            _allowed.WaitOne();
            if (nextFire != null && sw.Elapsed.TotalMilliseconds >= nextFire?.DelayMs)
            {
                var diff = sw.Elapsed.TotalMilliseconds;
                sw.Restart();

                foreach (var item in _subscribers)
                    item.DelayMs -= diff;

                foreach (var item in _subscribers.Where(p => p.DelayMs <= 0).ToList())
                {
                    item.Callback?.Invoke();
                    _subscribers.Remove(item);
                }
                nextFire = _subscribers.FirstOrDefault();
            }

            if (_adding)
                lock (_pendingSubscribers)
                {
                    _subscribers.AddRange(_pendingSubscribers);
                    _pendingSubscribers.Clear();
                    _subscribers.Sort();
                    _adding = false;
                    nextFire = _subscribers.FirstOrDefault();
                }

            if (nextFire == null || nextFire.DelayMs > HOT_LOOP_TRESHOLD_MS)
                _wakeFromColdLoop.WaitOne(1);
            else
                _wakeFromColdLoop.WaitOne(0);
        }
    }

    public void Dispose()
    {
        _disposing = true;
    }
}
0 голосов
/ 14 августа 2018

Предыдущий пример не работает, если частота не в миллисекундах;частота таймера перфорации редко указывается в мс.

private static Int64 m_iPerfFrequency = -1;

public static double GetPerfCounter()
{
    // see if we need to get the frequency
    if (m_iPerfFrequency < 0)
    {
        if (QueryPerformanceFrequency(out m_iPerfFrequency) == 0)
        {
            return 0.0;
        }
    }

    Int64 iCount = 0;
    if (QueryPerformanceCounter(out iCount) == 0)
    {
        return 0.0;
    }

    return (double)iCount / (double)m_iPerfFrequency;
}

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int QueryPerformanceCounter(out Int64 iCount);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int QueryPerformanceFrequency(out Int64 iFrequency);

Возвращает счетчик перфорации в секундах.Причина, по которой вы используете perf таймер, состоит в том, чтобы поделиться таймером с унаследованным кодом C ++ или получить более точный таймер, чем класс C # StopWatch.

0 голосов
/ 21 августа 2011

Вы можете использовать QueryPerformanceCounter () и QueryPerformanceTimer (), как описано в этой статье .

0 голосов
/ 21 августа 2011

Системные часы " тикают " с постоянной скоростью. Чтобы повысить точность зависящей от таймера функции * с, вызовите ** timeGetDevCaps *, который определяет минимальное поддерживаемое разрешение таймера. Затем вызовите timeBeginPeriod , установив разрешение таймера на минимум.

Осторожно: вызывая timeBeginPeriod, можно существенно повлиять на другие функции, зависящие от таймера, такие как системные часы, энергопотребление системы и планировщик. Таким образом, запустите ваше приложение с timeBeginPeriod и завершите его с timeEndPeriod

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