How can I give this time-critical procedure priority over other threads?
In order to write a MIDI sequencer I need a steady pulse that calls a timing routine that has absolute priority over anything else in the program and preferrably over anything in the computer. I do this by using TimeSetEvent like this:
TimeSetEvent (FInterval, 0, TimerUpdate, uInt32 (Self), TIME_PERIODIC);
where TimerUpdate is a callback that resumes a separate thread with priority tpTimeCritical and that one calls a routine (FOnTimer) in which to handle all MIDI events.
procedur开发者_运维知识库e TThreaded_Timer.Execute;
begin
if Assigned (FOnTimer) then
begin
while not Terminated do
begin
FOnTimer (Self);
if not Terminated then Suspend;
end; // while
end; // if
Terminate;
end; // Execute //
Although this construction is much better than some things I tried before it is still very sensitive to some events. To my surprise it stutters at each display of a hint. Why can a simple hint cause such an interruption of a time critical thread? Of course I can switch it off but which nasty surprises are still waiting for me?
Use the multimedia timer which is designed for this purpose. Delphi timers are awful and really only get attention during idle time. A thread-based timer is only useful when that thread gets attention. The MMTimer operates at kernel level and provides a callback that is really quite accutate. We use it for hardware sequencing automation control it is so good.
Here is my unit that implements an MMTimer as a more easy to use TTimer. Use 'Repeat' to make it single-shot or repetetive.
unit UArtMMTimer;
interface
uses
Classes,
SysUtils,
ExtCtrls,
MMSystem;
type
TArtMMTimer = class( TObject )
constructor Create;
destructor Destroy; override;
PRIVATE
FHandle : MMResult;
FRepeat : boolean;
FIntervalMS : integer;
FOnTimer : TNotifyEvent;
FEnabled : boolean;
procedure RemoveEvent;
procedure InstallEvent;
procedure DoOnCallback;
procedure SetEnabled( AState : boolean );
procedure SetIntervalMS( AValue : integer );
PUBLIC
property Enabled : boolean
read FEnabled
write SetEnabled;
property OnTimer : TNotifyEvent
read FOnTimer
write FOnTimer;
property IntervalMS : integer
read FIntervalMS
write SetIntervalMS;
end;
implementation
uses
Windows;
// TArtMMTimer
// --------------------------------------------------------------------
procedure MMTCallBack(uTimerID, uMessage: UINT;
dwUser, dw1, dw2: DWORD) stdcall;
var
Timer : TArtMMTimer;
begin
Timer := TArtMMTimer( dwUser );
Timer.DoOnCallback;
end;
constructor TArtMMTimer.Create;
begin
Inherited Create;
FIntervalMS := 100;
FRepeat := True;
end;
destructor TArtMMTimer.Destroy;
begin
FOnTimer := nil;
RemoveEvent;
Inherited Destroy;
end;
procedure TArtMMTimer.RemoveEvent;
begin
If FHandle <> 0 then
begin
timeKillEvent( FHandle );
FHandle := 0;
end;
end;
procedure TArtMMTimer.InstallEvent;
var
iFlags : integer;
begin
RemoveEvent;
If FRepeat then
iFlags := TIME_PERIODIC Or TIME_CALLBACK_FUNCTION
else
iFlags := TIME_CALLBACK_FUNCTION;
FHandle := timeSetEvent(
FIntervalMS,
0,
@MMTCallBack,
DWord(Self),
iFlags );
end;
procedure TArtMMTimer.SetEnabled( AState : boolean );
begin
If AState <> FEnabled then
begin
FEnabled := AState;
If FEnabled then
InstallEvent
else
RemoveEvent;
end;
end;
procedure TArtMMTimer.DoOnCallback;
var
NowHRCount, WaitHRCount,IntervalHRCount : THRCount;
begin
If Assigned( FOnTimer ) then
FOnTimer( Self );
end;
procedure TArtMMTimer.SetIntervalMS( AValue : integer );
begin
If AValue <> FIntervalMS then
begin
FIntervalMS := AValue;
If Enabled then
begin
Enabled := False;
Enabled := True;
end;
end;
end;
// End TArtMMTimer
// --------------------------------------------------------------------
end.
The accuracy of the multimedia timers is not that great.Here is an article that explains why.
Instead of relying on a timer to wake up your thread why don't you manage your sleep and wake times all within the thread itself?
Maybe something like this (in pseudo-code, sorry I don't know Delphi):
my_critical_thread()
{
while (true) {
time = get_current_time()
do_work();
time = interval - (get_current_time() - time)
if (time > 0)
sleep(time)
}
}
This should get you very close to your target interval if the thread is set to critical priority, assuming the work you do on each iteration leaves time for your other threads and the rest of the system to do their thing.
Good luck.
Set a timer to a slightly shorter time than required (10 ms less, for example).
When the timer occurs, raise thread priority to "above normal".
Calculate the remaining time to wait and execute Sleep with a slightly shorter interval (1 ms less, for example).
Now start waiting in a loop for the correct time. In each loop occurrence execute at least one asm pause; end; instruction to not push a core to the 100% usage.
When the time occurs, lower thread priority to "normal".
I think that's the best you can do without writing a device driver.
Thanks for all the suggestions. In order to test them I developed a small Delphi program in order to test the suggested algorithms. Four algorithms are tested
- Simple (by Brian Frost) - Uses the Multimedia timer (TimeSetEvent), which calls a callback that performs the timed task.
- Threaded (by Brian Pedersen) - As with simple, but the callback is called in a separate thread. The thread receives the highest priority possible (TimeCritical).
- Looping (by Miguel) - In this algorithm we don't trust the timer at all and write one ourselves. The callback is performed in a loop, after each call we examine how much time is still left until the next tick and wait until the next has to take place. The thread has highest priority. I used the asm pause; end suggestion from Gabr as a fast way of event processing.
- Fire & Forget (by myself) - The multimedia timer at each tick creates a separate thread with the highest priority, assigns it the callback and forgets about it. This has the advantage that even when a previous thread has not yet finished, a new one can start already and if you are lucky - on a new processor.
You can find the results here. Most timers run correct with a normal workload, though the simple multimedia timer shows the most variability. The looping timer is the most precise. All timers except the fire & forget run into problems when the workload results in work that lasts longer than the Interval. The best performance comes from the fire & forget algorithm. However, one should take care that no shared resources are used in the callback because the callback can be invoked multiple times when the workload increases. In fact in the current implementation MIDIOut can be invoked simultaneously so it should be surrounded by a critical section.
When another programs is run the timers show a greater variability. The looping timer still performs best.
When I add the timer to my original MIDI sequencer the original question remains als. Hints keep interrupting the timer as before though they did not so in the test version which you can download.
Parameters Constancy of Beat Workload
Timer N Interval Resolution WorkLoad Mean s.d. Min Max Mean s.d. Min Max
Simple 226 22 30 1000 22.001 0.001 21.996 22.009 0.093 0.036 0.079 0.302
Threaded 226 22 30 1000 22.001 0.004 21.964 22.031 0.091 0.032 0.079 0.253
Looping 227 22 30 1000 22.000 0.002 21.999 22.025 0.093 0.034 0.079 0.197
Fire & Forget 226 22 30 1000 22.001 0.008 21.964 22.042 0.091 0.031 0.079 0.186
Simple 226 22 15 1000 22.001 0.002 21.989 22.011 0.091 0.031 0.079 0.224
Threaded 226 22 15 1000 22.001 0.003 21.978 22.031 0.091 0.032 0.079 0.185
Looping 227 22 15 1000 22.000 0.001 21.999 22.015 0.092 0.034 0.079 0.209
Fire & Forget 226 22 15 1000 22.001 0.015 21.861 22.146 0.091 0.031 0.079 0.173
Simple 226 22 0 1000 22.001 0.001 21.997 22.005 0.091 0.030 0.079 0.190
Threaded 226 22 0 1000 22.001 0.003 21.979 22.029 0.091 0.031 0.079 0.182
Looping 227 22 0 1000 22.000 0.000 21.999 22.002 0.092 0.034 0.079 0.194
Fire & Forget 226 22 0 1000 22.001 0.026 21.747 22.256 0.090 0.030 0.079 0.180
Simple 226 22 30 10000 22.001 0.002 21.992 22.012 0.801 0.034 0.787 1.001
Threaded 226 22 30 10000 22.001 0.002 21.994 22.008 0.800 0.031 0.787 0.898
Looping 227 22 30 10000 22.000 0.000 21.999 22.000 0.802 0.034 0.787 0.919
Fire & Forget 226 22 30 10000 22.001 0.010 21.952 22.087 0.903 0.230 0.788 1.551
Simple 226 22 15 10000 22.001 0.002 21.984 22.020 0.810 0.081 0.788 1.417
Threaded 226 22 15 10000 22.001 0.006 21.981 22.073 0.800 0.031 0.788 0.889
Looping 227 22 15 10000 22.000 0.000 21.999 22.000 0.802 0.036 0.787 0.969
Fire & Forget 226 22 15 10000 22.001 0.009 21.914 22.055 0.799 0.030 0.788 0.885
Simple 226 22 0 10000 22.001 0.002 21.994 22.006 0.799 0.030 0.788 0.894
Threaded 226 22 0 10000 22.001 0.005 21.953 22.048 0.799 0.030 0.787 0.890
Looping 227 22 0 10000 22.000 0.000 21.999 22.002 0.801 0.034 0.787 0.954
Fire & Forget 226 22 0 10000 22.001 0.007 21.977 22.029 0.799 0.030 0.788 0.891
Simple 226 22 30 100000 22.001 0.002 21.988 22.017 7.900 0.052 7.879 8.289
Threaded 226 22 30 100000 22.001 0.003 21.967 22.035 7.897 0.036 7.879 8.185
Looping 227 22 30 100000 22.000 0.001 21.999 22.015 7.908 0.098 7.879 9.165
Fire & Forget 225 22 30 100000 22.001 0.007 21.960 22.027 7.901 0.038 7.880 8.061
Simple 227 22 15 100000 22.014 0.195 21.996 24.934 7.902 0.056 7.879 8.351
Threaded 226 22 15 100000 22.001 0.002 21.997 22.008 7.900 0.049 7.879 8.362
Looping 227 22 15 100000 22.000 0.000 22.000 22.000 7.900 0.046 7.879 8.229
Fire & Forget 225 22 15 100000 22.001 0.008 21.962 22.065 7.906 0.082 7.880 8.891
Simple 227 22 0 100000 22.018 0.261 21.937 25.936 7.901 0.050 7.879 8.239
Threaded 226 22 0 100000 22.001 0.001 21.998 22.005 7.897 0.031 7.879 7.987
Looping 227 22 0 100000 22.000 0.000 21.999 22.000 7.901 0.053 7.879 8.263
Fire & Forget 225 22 0 100000 22.001 0.007 21.967 22.032 7.900 0.044 7.880 8.308
Simple 63 22 30 1000000 78.027 6.801 24.938 80.730 77.754 8.947 7.890 80.726
Threaded 56 22 30 1000000 87.908 1.334 78.832 91.787 78.897 0.219 78.819 80.430
Looping 62 22 30 1000000 78.923 0.320 78.808 80.749 78.923 0.320 78.808 80.748
Fire & Forget 222 22 30 1000000 22.001 0.009 21.956 22.038 84.212 3.431 78.825 91.812
Simple 66 22 15 1000000 75.656 13.090 21.994 80.714 79.183 1.559 78.811 90.950
Threaded 56 22 15 1000000 87.841 1.204 78.991 88.011 78.849 0.043 78.812 79.003
Looping 62 22 15 1000000 78.880 0.207 78.807 80.442 78.880 0.207 78.807 80.441
Fire & Forget 222 22 15 1000000 22.001 0.978 11.975 32.042 84.915 3.569 78.816 90.917
Simple 66 22 0 1000000 75.681 12.992 21.991 80.778 79.213 1.400 78.807 87.766
Threaded 56 22 0 1000000 87.868 1.238 78.889 89.515 78.954 0.597 78.813 83.164
Looping 62 22 0 1000000 78.942 0.307 78.806 80.380 78.942 0.307 78.806 80.379
Fire & Forget 222 22 0 1000000 22.001 0.011 21.926 22.076 83.953 3.103 78.821 91.145
精彩评论