开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜