Подтвердить что ты не робот

Таймер высокого разрешения

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

4b9b3361

Ответ 2

Что касается информации, которую ОП конкретно спрашивал о классе 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: Следующий мой старый ответ, когда я думал, что OP говорит о сроках. Ниже приведена только полезная информация о сроках действия вещей, но она не предусматривает какого-либо способа запуска событий на регулярной основе. Для этой цели необходим класс 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 могут использоваться вместо неуправляемого API Win32 QueryPerformanceFrequency и QueryPerformanceCounter.

Ответ 3

Как насчет this one?

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);
    }
}

Ответ 4

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

  • Он работает на всех платформах и имеет высокую точность везде, где StopWatch.IsHighPrecision == true

  • Событие 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: введите описание изображения здесь

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

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

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

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

  • Использование ЦП эталона составляет 10% для интервала 0,5 мс и 0,1% для интервала 200 мс

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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++ или получить более точный таймер, чем класс С# StopWatch.

Ответ 8

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

Фон

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

Отдайте количество времени потока в горячем цикле другим потокам, как только в этом больше нет необходимости, вызывая 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;
    }
}