Страницы

Поиск по вопросам

среда, 1 января 2020 г.

Чем заменить Suspend и Resume при работе с потоками?

#delphi #многопоточность #delphi_xe


Есть такой метод в одном стареньком компоненте:

procedure TTangentThread.SetActive(Value: Boolean);
begin
 if Value <> FActive then
  begin
   FActive := Value;
   case FActive of
    False:
     begin
      FThread.Suspend;
      if (IsRuntime) and (Assigned(FOnSuspend)) then FOnSuspend(self);
     end;
    True:
     begin
      FThread.Resume;
      if (IsRuntime) and (Assigned(FOnResume)) then FOnResume(self);
     end;
   end;
  end;
end;


И он, в общем то, работает, но компилятор говорит, что Suspend и Resume устарели
и их лучше не использовать. Как это нынче правильно переписать, не используя "устаревших"
методов?
    


Ответы

Ответ 1



Suspend и Resume на самом деле выполняются операционной системой. Для Windows внутри этих методов вызываются SuspendThread и ResumeThread Функции "живее всех живых", но... использовать их действительно не стоит. Проблема в том, что они работают с потоком "здесь и сейчас". То есть - если команда Suspend застала поток посередине выполнения оператора (например - присвоения строки) то именно в этой точке поток заснет. Естественно, такая "произвольность" не способствует качественному выполнению приложения, особенно - если дополнительный и основной поток работают над разделяемыми (совместными) данными. Например, дополнительный поток начал изменять массив (строку, список и т.п.), в это время его "заснули", основной поток обнулил массив и разбудил дополнительный. В дополнительном потоке штатная проверка размерности уже пройдена, поэтому он не узнает о том, что того элемента, над которым поток пытается работать, уже нет. В результате - гарантированная порча памяти со всеми вытекающими последствиями. Именно поэтому нужно использовать объекты синхронизации: TCriticalSection, TMutex, TEvent, TSemaphore, TMultiReadExclusiveWriteSynchronizer, которыми ограждается доступ к разделяемым ресурсам и / или посылаются "правильные", в "нужные моменты времени" управляющие сигналы. Альтернатива (не кросс-платоформенная) - использовать механизм сообщений. В качестве примера: дополнительный поток по команде должен выполнить какую-то работу и ожидать новую команду: uses System.SyncObjs; type TmyThread = class(TThread) private FEvent: TEvent; // "синхронизатор" procedure DoWork; // в этом методе будет выполняться полезная работа protected procedure Execute; override; public constructor Create; destructor Destroy; override; procedure StartSingleWork; // этот метод вызывается "извне" end; { TmyThread } constructor TmyThread.Create; begin inherited Create(False); FEvent := TEvent.Create; // создаем сигнальное событие end; procedure TmyThread.Execute; begin while not Terminated do begin FEvent.WaitFor; // ожидаем, пока взведут событие FEvent.ResetEvent; // сбрасываем событие,чтобы // опять войти в ожидание на WaitFor if not Terminated then // и если поток не уничтожают DoWork; // делаем свое дело. end; end; procedure TmyThread.StartSingleWork; begin // кто-то извне хочет, чтобы поток выполнил свою работу FEvent.SetEvent; // выводим поток из спячки. // поток будет выведен из ожидания в WaitFor, // выполнит полезную работу и опять заснет. end; procedure TmyThread.DoWork; begin // здесь выполняется какая-то полезная работа // в контексте нашего дополнительного потока. end; destructor TmyThread.Destroy; begin Terminate; // начинаем уничтожение FEvent.SetEvent; // выводим поток из спячки while not Finished do // ждем, пока он завершится Sleep(0); FreeAndNil(FEvent); // и уничтожаем содержимое потока. inherited; end; В дополнение: на мой взгляд, лучшая из статей про многопоточность в Delphi (и не только). К сожалению - только в web.archive... Upd. Старый форум Винграда ожил, прямая ссылка на статью

Ответ 2



Эти методы не "устарели", дело здесь в другом. Насколько я знаю, они используются отладчиком для управления потоками во время отладки. И если эти методы используются в вашем приложении, можно нарваться на "непонятные глюки" во время отладки. Или вот что будет с вашим приложением, если вы приостановите поток в то время когда он занял какой-нибудь критически важный системный объект? По-хорошему, приостанавливать работу потока нельзя, можно "попросить" его остановиться в удобный для него момент. Для этого надо использовать объекты синхронизации, например TEvent. Поток в своем цикле проверяет состояние этого объекта синхронизации и либо ждет пока можно будет работать, либо работает. Если вам нужно только один раз запустить поток в нужный момент, то можно использовать метод Start. Если вам очень хочется избавиться от предупреждений компилятора, можно использовать свойство Suspended (Suspended := False; или Suspended := True;) вместо Suspend и Resume. Это уберет предупреждения компилятора, но не избавит от возможных проблем.

Комментариев нет:

Отправить комментарий