Как я могу дать этому срочному процессу приоритет над другими потоками? - PullRequest
3 голосов
/ 24 сентября 2011

Чтобы написать MIDI-секвенсор, мне нужен устойчивый импульс, который вызывает процедуру синхронизации, которая имеет абсолютный приоритет над всем остальным в программе и предпочтительно над всем на компьютере. Я делаю это с помощью TimeSetEvent, как это:

TimeSetEvent (FInterval, 0, TimerUpdate, uInt32 (Self), TIME_PERIODIC);

где TimerUpdate - это обратный вызов, который возобновляет отдельный поток с приоритетом tpTimeCritical и вызывает процедуру (FOnTimer), в которой обрабатываются все события MIDI.

procedure 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 //

Хотя эта конструкция намного лучше, чем некоторые вещи, которые я пробовал прежде, она все еще очень чувствительна к некоторым событиям. К моему удивлению, он заикается при каждом показе подсказки. Почему простой намек может вызвать такое прерывание потока, критичного ко времени? Конечно, я могу выключить его, но какие неприятные сюрпризы все еще ждут меня?

Ответы [ 4 ]

5 голосов
/ 25 сентября 2011

Используйте мультимедийный таймер, который предназначен для этой цели.Таймеры Delphi ужасны и действительно привлекают внимание только во время простоя.Таймер на основе потоков полезен только тогда, когда этот поток привлекает внимание.MMTimer работает на уровне ядра и обеспечивает обратный вызов, который является довольно точным.Мы используем его для управления аппаратным секвенированием, это так хорошо.

Вот мой модуль, который реализует MMTimer как более простой в использовании TTimer.Используйте «Повтор», чтобы сделать его однократным или повторяющимся.

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.
1 голос
/ 28 сентября 2011

Спасибо за все предложения.Чтобы протестировать их, я разработал небольшую программу Delphi , чтобы протестировать предложенные алгоритмы.Тестируются четыре алгоритма

  • Простой (Брайан Фрост) - использует мультимедийный таймер (TimeSetEvent), который вызывает обратный вызов, выполняющий синхронизированную задачу.- Как и простой, но обратный вызов вызывается в отдельном потоке.Поток получает максимально возможный приоритет (TimeCritical).
  • Looping (by Miguel) - в этом алгоритме мы вообще не доверяем таймеру и пишем его сами.Обратный вызов выполняется в цикле, после каждого вызова мы проверяем, сколько времени осталось до следующего тика, и ждем, пока следующий не произойдет.Тема имеет наивысший приоритет.Я использовал асм паузу;конец предложения от Габра как быстрый способ обработки событий.
  • Fire & Forget (самостоятельно) - мультимедийный таймер на каждом тике создает отдельный поток с наивысшим приоритетом, назначает ему обратный вызов и забывает об этом.Это дает преимущество в том, что даже если предыдущий поток еще не закончен, новый может уже начаться, а если вам повезет - на новом процессоре.

Вы можете найти результаты здесь..Большинство таймеров работают правильно при нормальной рабочей нагрузке, хотя простой мультимедийный таймер показывает наибольшую вариабельность.Таймер зацикливания является наиболее точным.Все таймеры, кроме запуска и забывания, сталкиваются с проблемами, когда рабочая нагрузка приводит к работе, которая длится дольше, чем интервал.Наилучшая производительность достигается благодаря алгоритму «забей и забудь».Однако следует позаботиться о том, чтобы в обратном вызове не использовались общие ресурсы, поскольку обратный вызов может вызываться несколько раз при увеличении рабочей нагрузки.Фактически в текущей реализации MIDIOut может быть вызван одновременно, поэтому он должен быть окружен критическим разделом.

При запуске другой программы таймеры показывают большую изменчивость.Циклический таймер по-прежнему работает лучше всего.

Когда я добавляю таймер в свой оригинальный MIDI-секвенсор, исходный вопрос остается неизменным.Подсказки продолжают прерывать таймер, как и раньше, хотя это не так в тестовой версии, которую вы можете загрузить.

                  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
1 голос
/ 24 сентября 2011

Установите таймер на немного более короткое время, чем требуется (например, на 10 мс меньше).

Когда таймер срабатывает, увеличьте приоритет нити до «выше нормы».

Рассчитайте оставшееся время ожидания и выполните Sleep с немного более коротким интервалом (например, менее 1 мс).

Теперь начните ждать в цикле правильное время. В каждом вхождении цикла выполните хотя бы одну паузу asm; конец; инструкция не выдвигать ядро ​​на 100% использование.

Когда наступит время, понизьте приоритет нити до «нормальный».

Я думаю, что это лучшее, что вы можете сделать без написания драйвера устройства.

1 голос
/ 24 сентября 2011

Точность мультимедийных таймеров не так уж велика. Здесь - статья, объясняющая почему.

Вместо того, чтобы полагаться на таймер для пробуждения вашего потока, почему вы не управляете временем сна и пробуждения все в самом потоке?

Может быть, что-то вроде этого (в псевдокоде, извините, я не знаю Delphi):

my_critical_thread()
{
    while (true) {
        time = get_current_time()
        do_work();
        time = interval - (get_current_time() - time)
        if (time > 0)
            sleep(time)
    }
}

Это должно очень приблизить вас к целевому интервалу, если для потока установлен критический приоритет, при условии, что работа, которую вы выполняете на каждой итерации, оставляет время для ваших других потоков и остальной системы, чтобы сделать свое дело.

Удачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...