Ответ 1
Для точного измерения времени вам нужно использовать класс секундомера MSDN
Выполнение следующего (слегка псевдо) кода приводит к следующим результатам. Я шокирован тем, насколько нецелесообразен таймер (выигрывает ~ 14 мс каждый Tick
).
Есть ли что-нибудь более точное там?
void Main()
{
var timer = new System.Threading.Timer(TimerCallback, null, 0, 1000);
}
void TimerCallback(object state)
{
Debug.WriteLine(DateTime.Now.ToString("ss.ffff"));
}
Sample Output:
...
11.9109
12.9190
13.9331
14.9491
15.9632
16.9752
17.9893
19.0043
20.0164
21.0305
22.0445
23.0586
24.0726
25.0867
26.1008
27.1148
28.1289
29.1429
30.1570
31.1710
32.1851
Для точного измерения времени вам нужно использовать класс секундомера MSDN
Я также пропустил класс с точностью до 1 мс. Я взял код Hans Passant с форума
https://social.msdn.microsoft.com/Forums/en-US/6cd5d9e3-e01a-49c4-9976-6c6a2f16ad57/1-millisecond-timer
и завернул его в класс для удобства использования в вашей форме. Вы можете легко настроить несколько таймеров, если хотите. В приведенном ниже примере кода я использовал 2 таймера. Я тестировал его, и он работает нормально.
// AccurateTimer.cs
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace YourProjectsNamespace
{
class AccurateTimer
{
private delegate void TimerEventDel(int id, int msg, IntPtr user, int dw1, int dw2);
private const int TIME_PERIODIC = 1;
private const int EVENT_TYPE = TIME_PERIODIC;// + 0x100; // TIME_KILL_SYNCHRONOUS causes a hang ?!
[DllImport("winmm.dll")]
private static extern int timeBeginPeriod(int msec);
[DllImport("winmm.dll")]
private static extern int timeEndPeriod(int msec);
[DllImport("winmm.dll")]
private static extern int timeSetEvent(int delay, int resolution, TimerEventDel handler, IntPtr user, int eventType);
[DllImport("winmm.dll")]
private static extern int timeKillEvent(int id);
Action mAction;
Form mForm;
private int mTimerId;
private TimerEventDel mHandler; // NOTE: declare at class scope so garbage collector doesn't release it!!!
public AccurateTimer(Form form,Action action,int delay)
{
mAction = action;
mForm = form;
timeBeginPeriod(1);
mHandler = new TimerEventDel(TimerCallback);
mTimerId = timeSetEvent(delay, 0, mHandler, IntPtr.Zero, EVENT_TYPE);
}
public void Stop()
{
int err = timeKillEvent(mTimerId);
timeEndPeriod(1);
System.Threading.Thread.Sleep(100);// Ensure callbacks are drained
}
private void TimerCallback(int id, int msg, IntPtr user, int dw1, int dw2)
{
if (mTimerId != 0)
mForm.BeginInvoke(mAction);
}
}
}
// FormMain.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace YourProjectsNamespace
{
public partial class FormMain : Form
{
AccurateTimer mTimer1,mTimer2;
public FormMain()
{
InitializeComponent();
}
private void FormMain_Load(object sender, EventArgs e)
{
int delay = 10; // In milliseconds. 10 = 1/100th second.
mTimer1 = new AccurateTimer(this, new Action(TimerTick1),delay);
delay = 100; // 100 = 1/10th second.
mTimer2 = new AccurateTimer(this, new Action(TimerTick2), delay);
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
mTimer1.Stop();
mTimer2.Stop();
}
private void TimerTick1()
{
// Put your first timer code here!
}
private void TimerTick2()
{
// Put your second timer code here!
}
}
}
Я думаю, что другие ответы не позволяют решить, почему 14 миллисекунд запустили каждую итерацию кода OP; это не из-за неточных системных часов (и DateTime.Now
не является неточным, если только вы не отключили службы NTP или не установили неправильный часовой пояс или что-то глупое), это только неточно).
Даже с неточными системными часами (используя DateTime.Now
, или с помощью солнечного элемента, подключенного к АЦП, чтобы определить, насколько высока солнце в небе или делит время между пиковыми приливами или...), код, следующий за этим шаблоном, будет иметь среднее значение нуля (он будет абсолютно точным ровно через одну секунду между тиками в среднем):
var interval = new TimeSpan(0, 0, 1);
var nextTick = DateTime.Now + interval;
while (true)
{
while ( DateTime.Now < nextTick )
{
Thread.Sleep( nextTick - DateTime.Now );
}
nextTick += interval; // Notice we're adding onto when the last tick was supposed to be, not when it is now
// Insert tick() code here
}
(Если вы копируете и вставляете это, следите за случаями, когда ваш тиковый код занимает больше времени interval
для выполнения. Я оставлю его как упражнение для чтения, чтобы найти простые способы сделать это пропускает столько ударов, сколько требуется для nextTick
, чтобы приземлиться в будущем)
Я предполагаю, что внедрение Microsoft System.Threading.Timer следует именно этому шаблону. Этот шаблон всегда будет иметь скорость даже с совершенно точным и совершенно точным системным таймером (поскольку для выполнения даже операции добавления требуется только время):
var interval = new TimeSpan(0, 0, 1);
var nextTick = DateTime.Now + interval;
while (true)
{
while ( DateTime.Now < nextTick )
{
Thread.Sleep( nextTick - DateTime.Now );
}
nextTick = DateTime.Now + interval; // Notice we're adding onto .Now instead of when the last tick was supposed to be. This is where slew comes from
// Insert tick() code here
}
Итак, для людей, которые могут быть заинтересованы в том, чтобы перевернуть свой собственный таймер, не следуйте этому второму шаблону.
Как отмечали другие плакаты, класс Stopwatch
дает большую точность измерения времени, но не помогает с точностью, если следовать за неправильным шаблоном. Но, как @Shahar сказал, это не похоже на то, что вы когда-нибудь начнете получать совершенно точный таймер, поэтому вам нужно переосмыслить ситуацию, если совершенная точность - это то, что вы после.
Обратите внимание, что Microsoft не очень много говорит о внутренних компонентах класса System.Threading.Timer, поэтому я об этом размышляю, но если он уклоняется от утки, то это, вероятно, утка. Кроме того, я понимаю, что это уже несколько лет, но это по-прежнему актуальный (и я думаю, без ответа) вопрос.
Изменить: Изменена ссылка на ответ @Shahar
Изменить: у Microsoft есть исходный код для большого количества материалов в Интернете, в том числе System.Threading.Timer, для тех, кто заинтересован в том, чтобы видеть как Microsoft реализовала этот таймер с таймером
Таймер и DateTime не имеют достаточной точности для вашей цели. Вместо этого попробуйте Stopwatch. Более подробную информацию смотрите в следующей статье:
http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx
Не тот таймер, который является неточным, но DateTime.Now, который имеет рекламируемый допуск 16 мс.
Вместо этого я бы использовал свойство Environment.Ticks для измерения циклов процессора во время этого теста.
Изменить: Environment.Ticks также основывается на системном таймере и может иметь такие же проблемы с точностью, как DateTime.Now. Я бы посоветовал выбрать StopWatch
, как и многие другие ответчики.
Настольная операционная система (например, окна) - это операционная система не. что означает, что вы не можете ожидать полной точности, и вы не можете заставить планировщик запускать ваш код в точной миллисекунде, которую вы хотите. Специально в .NET-приложении, которое не является детерминированным... например, в любое время, когда GC может начать сбор, компиляция JIT может быть немного медленнее или немного быстрее....
Вот еще один подход. Точно в течение 5-20 мс на моей машине.
public class Run
{
public Timer timer;
public Run()
{
var nextSecond = MilliUntilNextSecond();
var timerTracker = new TimerTracker()
{
StartDate = DateTime.Now.AddMilliseconds(nextSecond),
Interval = 1000,
Number = 0
};
timer = new Timer(TimerCallback, timerTracker, nextSecond, -1);
}
public class TimerTracker
{
public DateTime StartDate;
public int Interval;
public int Number;
}
void TimerCallback(object state)
{
var timeTracker = (TimerTracker)state;
timeTracker.Number += 1;
var targetDate = timeTracker.StartDate.AddMilliseconds(timeTracker.Number * timeTracker.Interval);
var milliDouble = Math.Max((targetDate - DateTime.Now).TotalMilliseconds, 0);
var milliInt = Convert.ToInt32(milliDouble);
timer.Change(milliInt, -1);
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
}
public static int MilliUntilNextSecond()
{
var time = DateTime.Now.TimeOfDay;
var shortTime = new TimeSpan(0, time.Hours, time.Minutes, time.Seconds, 0);
var oneSec = new TimeSpan(0, 0, 1);
var milliDouble = (shortTime.Add(oneSec) - time).TotalMilliseconds;
var milliInt = Convert.ToInt32(milliDouble);
return milliInt;
}
}
Я сделал для этого класс, и, похоже, он работает нормально. Нет никакой неточности:
class AccurateTimer
{
public event EventHandler<EventArgs> Tick;
public bool Running { get; private set; }
public int Interval { get; private set; }
public AccurateTimer(int interval_ = 1000)
{
Running = false;
Interval = interval_;
}
public void Start()
{
Running = true;
Thread thread = new Thread(Run);
thread.Start();
}
public void Stop()
{
Running = false;
}
private void Run()
{
DateTime nextTick = DateTime.Now.AddMilliseconds(Interval);
while (Running)
{
if (DateTime.Now > nextTick)
{
nextTick = nextTick.AddMilliseconds(Interval);
OnTick(EventArgs.Empty);
}
}
}
protected void OnTick(EventArgs e)
{
EventHandler<EventArgs> copy = Tick;
if (copy != null)
{
copy(this, e);
}
}
}
Это может быть не лучшее решение.