Example of a custom high precision timer timer in C # tutorial _c# tutorial

Source: Internet
Author: User
Tags net thread set time time interval

1. Background

There are 3 types of timers in C #:
(1) defined in System.Windows.Forms
(2) defined in the System.Threading.Timer class
(3) defined in the System.Timers.Timer class

A Timer is used to trigger an event at a user-defined event interval. Windows timers are designed for single-threaded environments, where UI threads are used to perform processing. It requires that the user code have an available UI message pump, always operate in the same thread, or marshal the call to another thread.

When using this timer, use the control's tick event to perform a polling operation or to display the splash screen within a specified time. Whenever the Enabled property is set to True and the interval property is greater than 0 o'clock, the Tick event is raised, and the time interval that is raised is based on the Interval property setting.
System.Windows.Forms.Timer is applied to WinForm, he is implemented through the Windows messaging mechanism, similar to the Timer control in VB or Delphi, the internal use of API SetTimer implementation. His main disadvantage is that the timing is imprecise and that there must be a message loop that the console application (console application) cannot use.
System.Timers.Timer and System.Threading.Timer are similar, they are implemented through the. NET Thread pool, lightweight, with no special needs for applications or messages. System.Timers.Timer can also be applied to WinForm, completely replacing the timer control above.

However, its precision is not high (generally 15ms or so), it is difficult to meet the needs of some scenarios.

When performing media playback, rendering animations, profiling, and interacting with the hardware, you may need the following precision timers for 10ms. There is no discussion as to whether the demand is reasonable, it is a real problem, and there is a considerable amount of discussion about it, indicating that it is a real demand. However, it is not an easy thing to achieve.

This does not involve the kernel-driven level of timers, but only the high-precision timer implementations in the. NET managed Environment at the application level.

Windows is not a real-time operating system, so no solution can absolutely guarantee the accuracy of the timer, only to minimize the error. Therefore, the stability of the system can not rely entirely on the timer, must consider the loss of synchronization processing.

2. Wait for strategy

To achieve high-precision timer, there must be waiting and timing of the two basic functions. Wait to skip a certain time interval, time can be checked to adjust the wait time.

The wait strategy is actually two kinds:

Spin wait: Let the CPU idle consumption time, occupy a lot of CPU time, but the time is highly controllable.
Blocking wait: The thread enters the blocking state, sells the CPU time slice, waits for a certain time, and then dispatches the operating system back to the running state. Blocking does not occupy the CPU, but requires operating system scheduling, time is difficult to control.
We can see that both have their advantages and disadvantages, and should be implemented according to different requirements.

And the timing mechanism can be said to be able to use only one, is the stopwatch class. It uses the system API queryperformancecounter/queryperformancefrequency to perform high-precision timing, relies on hardware, and its precision can be as high as dozens of nanoseconds, ideal for high-precision timer implementations.

So the difficulty is to wait for the strategy, the following first analysis of the simple spin wait.

2.1 Spin Wait

You can use thread.spinwait (int iteration) to spin, that is, let the CPU idling in a loop, the iteration parameter is the number of iterations. Many of the synchronization constructs in the. NET Framework are used to wait for a short time, Reduce the overhead of context switching.

It is difficult to calculate the time consumed based on iteration, because the CPU speed may be dynamic. So you need to use stopwatch in combination. Pseudo code is as follows:

var wait start time = current timing;
While (current timing-wait start time) < time to wait) {spin
  ;
}

Write the actual code:

void Spin (stopwatch w, int duration)
{
  var current = W.elapsedmilliseconds;
  while ((W.elapsedmilliseconds-current) < duration)
    thread.spinwait (a);
}

Here the W is an already started stopwatch, in order to demonstrate the simple use of the Elapsedmilliseconds attribute, the precision is millisecond, using the Elapsedticks property can achieve higher precision (microsecond level).

However, as mentioned earlier, this is high precision but at the cost of CPU time, so the implementation of the timer will make a CPU core full load (if the task is not blocked). The equivalent of wasting a core, in some cases not realistic (such as the core of a small or even a single core virtual machine), so need to consider blocking the wait.

2.2 Blocking waiting

Blocking waiting will give control to the operating system, so you must ensure that the operating system is able to dispatch the timer thread back to the running State in a timely manner. By default, Windows system timer precision is 15.625ms, which means that the time slice is this size. If the thread is blocked, the time slice is assigned to wait, and the time is scheduled to run at least one slice of 15.625ms. The length of the time slice must be reduced to achieve higher precision.

The system timer precision can be modified to 1ms through the system API Timebeginperiod (it uses a ntsettimerresolution that does not give a document, and this API can be modified to 0.5ms). Use Timeendperiod restore when not needed.

Modifying the system timer precision has side effects. It will increase the cost of context switching, increase power consumption, and reduce the overall performance of the system. However, many programs have to do this because there is no other way to achieve higher timer accuracy. Many programs, such as WPF based programs (including Visual Studio), applications using the Chromium kernel (Chrome, QQ), multimedia players, games, and so on, can modify the system timer precision to 1ms within a certain amount of time. (view method See later)

So actually this side effect in the desktop environment has become the norm. and starting with Windows 8, this side effect has weakened.

Under the premise of 1ms system timer precision, blocking waits can be implemented in three ways:

(1) Thread.Sleep
(2) WaitHandle.WaitOne
(3) Socket.Poll
In addition, the multimedia timer timeSetEvent also uses a blocking approach.

(1) Thread.Sleep

Its parameters are measured in milliseconds, so it can only be accurate to 1ms. But in fact very unstable, thread.sleep (1) will be in 1ms and 2ms two of the state between the beat, that is, may produce +1ms many errors.

It was found that there was no task load (pure loop call Sleep (1)), the length of blocking was stable at 2ms, and at least 1ms was blocked when there was a task load. This is different from the other two ways of blocking, as detailed in the following article.

If you need to fix this error, you can use Sleep (n-1) when blocking n milliseconds, and through the stopwatch timer, the rest of the wait time is supplemented with a (0), Thread.yield, or spin.

Sleep (0) gives the remaining CPU time slices to the same thread as the priority, while thread.yield the remaining CPU time slices to the threads running on the same core. At the end of the transfer period, it is scheduled to be reset. In general, the entire process can be completed within 1ms.

Thread.Sleep (0) and Thread.yield are very unstable in the case of high CPU load, and the measurement may block up to 6ms of time, so more errors may occur. Therefore, the error correction is best achieved by the spin method.

(2) WaitHandle.WaitOne

WaitHandle.WaitOne is similar to Thread.Sleep, and the parameter is a millisecond unit.

The difference is that in the absence of a task load (pure circular call to WaitOne (1)), the length of the block is stable at 1.5ms, and when there is a task load, it is possible to block up to 0 of the time (guess it only blocks until the end of the current time slice, and no specific documentation is found). So the length of time it blocks is 0 to 2ms.

WaitHandle.WaitOne (0) is used to test the state of the wait handle, it does not block, so use it to make error correction similar to spin, but not as direct use of spin reliable.

(3) Socket.Poll

The parameter of the Socket.Poll method is in microseconds, in theory, it uses the hardware of the network card to timing, the precision is very high. However, because the blocking implementation still relies on the thread, it can only achieve 1ms precision.

Its advantage is more stable than thread.sleep and WaitHandle.WaitOne, the error is also smaller, you can not need to fix, but to occupy a Socket port.

In the absence of a task load (pure circular call to poll (1)), the length of the block is stable at 1ms, and when there is a task load, it is similar to WaitOne, and may only block nearly 0 of the time. So the length of time it blocks is 0 to 1ms.

Socket.Poll (0) is used to test the state of the Socket, but it will block and possibly block up to 6ms, so it cannot be used for error correction.

2.3timeSetEvent

timeSetEvent, like the Timebeginperiod mentioned earlier, belongs to the multimedia timer function provided by Winmm.dll. It can be used directly as a timer, but also to provide 1ms accuracy. Use Timekillevent to shut down when you don't need it.

Its stability and precision is also very high, if you need 1ms timing, and can not use the spin, then this is the ideal solution.

Although MSDN says timeSetEvent is an obsolete method, it should be replaced with Createtimerqueuetimer. But Createtimerqueuetimer precision and stability are not as good as the multimedia timer, so when you need high-precision, you can only use timeSetEvent.

3, Timer implementation

It should be noted that regardless of the spin or blocking, it is clear that the timer should be running on a separate thread, can not interfere with the work of the user thread. For high-precision timers, the threads that trigger events to perform tasks are generally within the timer thread, rather than using separate task threads.

This is because in a high-precision timing scenario, the time spent executing a task is likely to be greater than the timer interval, and if the task is performed by default on another thread, it may cause a large number of threads to be consumed. Therefore, the control should be given to the user, allowing the user to schedule the thread to execute the task when needed.

3.1 Trigger Mode

Because the timer thread executes the task, the timer triggers three different patterns. Here are their descriptions and the main loop pseudocode:

(1) Fixed time frame
For example interval 10ms, Task 7-12ms, will wait for 10ms, task 7ms, wait 3ms, Task 12ms (timeout 2ms lost sync), task 7ms, wait for 1ms (back to sync), task 7ms, wait 3ms 、... For is to try to follow a set time frame to perform the task, as long as the task does not always time out, can return to the original timeframe.

var Next frame time = 0;
while (timer Open)
{
  Next frame time = = Interval;
  while (current timing < Next frame time)
  {
    wait;
  }
  Trigger task;

(2) The time frame can be postponed:
The example above will follow the wait 10ms, task 7ms, wait 3ms, Task 12ms (timeout, postpone time frame 2ms), Task 7ms, wait 3ms 、... For A timed task will postpone the time frame.

var Next frame time = 0;
while (timer Open)
{
  Next frame time = = Interval;
  if (Next frame time < current timer)
    Next Frame time = current timing while
  (current timing < Next frame time)
  {
    wait;
  }
  Trigger task;

(3) Fixed wait time
The above example will follow the wait 10ms, task 7ms, wait 10ms, task 12ms, wait 10ms, task 7ms ... For The wait time is always the same.

while (timer Open)
{
  var wait start time = current timing;
  While (current timing-wait start time) < interval)
  {
    wait;
  }
  Trigger task;
Or:
var next frame time = 0;
while (timer Open)
{
  Next frame time = = Interval;
  while (current timing < Next frame time)
  {
    wait;
  }
  Trigger a task;
  Next Frame time = current timing;
}

If the multimedia timer (timesetevent) is used, it will implement the first pattern, while the other wait strategies can implement all three modes, depending on the requirements.

The wait in the while loop can use spin or block, or it can be combined to balance precision, stability, and CPU overhead.

In addition, from the above pseudo code can be seen, the implementation of these three modes can be unified, can be done according to the situation to switch.

3.2-Thread Priority

It is best to increase the thread priority to ensure that the timer can work stably and reduce the chance of being preempted. It should be noted, however, that this can lead to starvation of low-priority threads when CPU resources are low. This means that high priority threads cannot be allowed to wait for low-priority threads to change state, and it is very likely that low-priority threads will not be able to run, leading to deadlocks or similar deadlocks. (see a similar example of starvation)

The final priority of the thread is related to the priority of the process, so it is sometimes necessary to increase the process priority (see the thread priority description of the multithreaded series in C #).

4. Other

There are two more points to note:

(1) Thread safety: The timer runs on a stand-alone thread, and its exposed members should implement thread safety, otherwise a problem may arise when the timer runs.
(2) Timely release of resources: Multimedia timer, wait handle, thread, etc. these are system resources, should be released/destroyed in time when they do not need them.
How do I view system timer accuracy?

A simple view can use the Clockres in the Sysinternals Toolkit, which displays the following information:

Maximum Timer interval:15.625 ms
Minimum timer interval:0.500 ms Current
timer interval:15.625 ms

//or

Maximum Timer interval:15.625 ms
Minimum timer interval:0.500 ms Current
timer interval:1.000 ms

If you want to see which programs request higher system timer precision, run:

powercfg energy-duration 5

It monitors the system's energy consumption by 5s, and then generates a Energy-report.html analysis report in the current directory to open it for viewing.

Find the warning section inside and there will be a platform timer resolution: An unfinished Timer request (Platform Timer resolution:outstanding timer requests) information.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.