Страницы

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

среда, 9 января 2019 г.

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

Есть такой метод в одном стареньком компоненте:
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 устарели и их лучше не использовать. Как это нынче правильно переписать, не используя "устаревших" методов?


Ответ

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. Старый форум Винграда ожил, прямая ссылка на статью

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

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