-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHighResolutionTimer.cs
More file actions
215 lines (182 loc) · 5.91 KB
/
HighResolutionTimer.cs
File metadata and controls
215 lines (182 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace HighResolutionTimer
{
/// <summary>
/// Hight precision non overlapping timer
/// Came from
/// https://stackoverflow.com/a/41697139/548894
/// https://stackoverflow.com/a/45097518
/// </summary>
/// <remarks>
/// This implementation guaranteed that Elapsed events
/// are not overlapped with different threads.
/// Which is important, because a state of the event handler attached to Elapsed,
/// may be left unprotected of multi threaded access
/// </remarks>
public class HighResolutionTimer
{
/// <summary>
/// Tick time length in [ms]
/// </summary>
public static readonly double TickLength = 1000f / Stopwatch.Frequency;
/// <summary>
/// Tick frequency
/// </summary>
public static readonly double Frequency = Stopwatch.Frequency;
/// <summary>
/// True if the system/operating system supports HighResolution timer
/// </summary>
public static bool IsHighResolution = Stopwatch.IsHighResolution;
/// <summary>
/// Invoked when the timer is elapsed
/// </summary>
public event EventHandler<HighResolutionTimerElapsedEventArgs> Elapsed;
/// <summary>
/// The interval of timer ticks [ms]
/// </summary>
private volatile float _interval;
/// <summary>
/// The timer is running
/// </summary>
private volatile bool _isRunning;
/// <summary>
/// Execution thread
/// </summary>
private Thread _thread;
/// <summary>
/// Creates a timer with 1 [ms] interval
/// </summary>
public HighResolutionTimer() : this(1f)
{
}
/// <summary>
/// Creates timer with interval in [ms]
/// </summary>
/// <param name="interval">Interval time in [ms]</param>
public HighResolutionTimer(float interval)
{
Interval = interval;
}
/// <summary>
/// The interval of a timer in [ms]
/// </summary>
public float Interval
{
get
{
return _interval;
}
set
{
if (value < 0f || Single.IsNaN(value))
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_interval = value;
}
}
/// <summary>
/// True when timer is running
/// </summary>
public bool IsRunning => _isRunning;
/// <summary>
/// If true, sets the execution thread to ThreadPriority.Highest
/// (works after the next Start())
/// </summary>
/// <remarks>
/// It might help in some cases and get things worse in others.
/// It suggested that you do some studies if you apply
/// </remarks>
public bool UseHighPriorityThread { get; set; } = false;
/// <summary>
/// Starts the timer
/// </summary>
public void Start()
{
if (_isRunning) return;
_isRunning = true;
_thread = new Thread(ExecuteTimer)
{
IsBackground = true,
};
if (UseHighPriorityThread)
{
_thread.Priority = ThreadPriority.Highest;
}
_thread.Start();
}
/// <summary>
/// Stops the timer
/// </summary>
/// <remarks>
/// This function is waiting an executing thread (which do to stop and join.
/// </remarks>
public void Stop(bool joinThread = true)
{
_isRunning = false;
// Even if _thread.Join may take time it is guaranteed that
// Elapsed event is never called overlapped with different threads
if (joinThread && Thread.CurrentThread != _thread)
{
_thread.Join();
}
}
private void ExecuteTimer()
{
float nextTrigger = 0f;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
while (_isRunning)
{
nextTrigger += _interval;
double elapsed;
while (true)
{
elapsed = ElapsedHiRes(stopwatch);
double diff = nextTrigger - elapsed;
if (diff <= 0f)
break;
if (diff < 1f)
Thread.SpinWait(10);
else if (diff < 5f)
Thread.SpinWait(100);
else if (diff < 15f)
Thread.Sleep(1);
else
Thread.Sleep(10);
if (!_isRunning)
return;
}
double delay = elapsed - nextTrigger;
Elapsed?.Invoke(this, new HighResolutionTimerElapsedEventArgs(delay));
if (!_isRunning)
return;
// restarting the timer in every hour to prevent precision problems
if (stopwatch.Elapsed.TotalHours >= 1d)
{
stopwatch.Restart();
nextTrigger = 0f;
}
}
stopwatch.Stop();
}
private static double ElapsedHiRes(Stopwatch stopwatch)
{
return stopwatch.ElapsedTicks * TickLength;
}
}
public class HighResolutionTimerElapsedEventArgs : EventArgs
{
/// <summary>/// Real timer delay in [ms]/// </summary>
public double Delay { get; }
internal HighResolutionTimerElapsedEventArgs(double delay)
{
Delay = delay;
}
}
}