Глава 5. Снова о разрушении потока. Тупик или зацикливание (Deadlock).

Содержание:

  • Метод WaitFor.
  • Контролируемое завершение потока - Подход 2.
  • Введение в обработку сообщений и отложенное уведомление.
  • WaitFor может вызвать долгую задержку.
  • Вы заметили ошибку?
  • Как избежать такого тупика.


Метод WaitFor.

OnTerminate, как обсуждалось в предыдущей части, полезно, если вы используете поток в режиме "выполнить и забыть", с автоматическим разрушением. Но что, если в некий момент выполнения главного потока VCL вы должны быть уверены, что все остальные потоки завершены? Решение состоит в использовании метода WaitFor, который пригодится в следующих случаях:

  • Главному потоку VCL нужен доступ к объекту рабочего потока после его остановки для чтения или записи содержащихся в нем данных .
  • Принудительное завершение потоков при закрытии программы неприемлемо.


Попросту говоря, когда поток А вызывает метод WaitFor потока B, сам он приостановливается, пока поток B не завершится. И когда поток А продолжит свое выполнение, можно быть уверенным, что результаты из потока B можно прочесть, и что объект потока B можно уничтожать. Обычно при завершении программы основной поток VCL вызывает Terminate всех вторичных потоков, и затем ожидает их завершения (WaitFor), после чего осуществляется выход из программы.

Контролируемое завершение потока - Подход 2.

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

Выделить всёРазвернуть кодкод Pascal/Delphi
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
unit PrimeForm;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, PrimeThread;
 
const
  WM_THREAD_COMPLETE = WM_APP + 5437{ Just a magic number }
 
type
  TPrimeFrm = class(TForm)
    NumEdit: TEdit;
    SpawnButton: TButton;
    ResultsMemo: TMemo;
    procedure SpawnButtonClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private declarations }
    FThread: TPrimeThrd;
    procedure HandleThreadCompletion(var Message: TMessage); message WM_THREAD_COMPLETE;
  public
    { Public declarations }
  end;
 
var
  PrimeFrm: TPrimeFrm;
 
implementation
 
{$R *.DFM}
 
procedure TPrimeFrm.HandleThreadCompletion(var Message: TMessage);
begin
  if Assigned(FThread) then
  begin
    FThread.WaitFor;
    FThread.Free;
    FThread := nil;
  end;
end;
 
procedure TPrimeFrm.SpawnButtonClick(Sender: TObject);
 
begin
  if not Assigned(FThread) then
  begin
    FThread := TPrimeThrd.Create(True);
    FThread.FreeOnTerminate := false;
    try
      with FThread do
      begin
        TestNumber := StrToInt(NumEdit.Text);
        Resume;
      end;
    except on EConvertError do
      begin
        FThread.Free;
        FThread := nil;
        ShowMessage('That is not a valid number!');
      end;
    end;
  end;
end;
 
procedure TPrimeFrm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := true;
  if Assigned(FThread) then
  begin
    if MessageDlg('Threads active. Do you still want to quit?',
      mtWarning, [mbYes, mbNo], 0) = mrNo then
      CanClose := false;
  end;
  {Sleep(50000);}{Line C}
  if CanClose then
  begin
    if Assigned(FThread) then
    begin
      FThread.Terminate;
      FThread.WaitFor;
      FThread.Free;
      FThread := nil;
    end;
  end;
end;
 
end.


Можно увидеть несколько отличий от предыдущего примера:

  • В начале модуля объявлено "магическое число" . Это относительный номер сообщения, а значение его не важно, главное, чтобы оно было уникальным.
  • Вместо счетчика потоков мы следим только за одним потоком, которому отвечает переменная FThread главной формы.
  • Мы хотим, чтобы в каждый момент исполнялся только один поток, поскольку имеется единственная переменная, указывающая на рабочий поток. Поэтому код перед созданием потока проверяет, нет ли уже запущенных потоков.
  • Код создания потока не устанавливает свойство FreeOnTerminate в True, вместо этого основной поток VCL будет освобождать рабочий поток позже.
  • У главной формы есть обработчик сообщения, который ждет завершения рабочего потока, и затем разрушает его.
  • Соответственно, код, исполняемый, когда пользователь хочет закрыть программу, ожидает завершения рабочего потока и освобождает его.

Учтите эти пункты, а здесь код рабочего потока

Выделить всёРазвернуть кодкод Pascal/Delphi
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
unit PrimeThread;
 
interface
 
uses
  Classes;
 
type
  TPrimeThrd = class(TThread)
  private
    FTestNumber: integer;
    FResultString: string;
  protected
    function IsPrime: boolean;
    procedure UpdateResults;
    procedure Execute; override;
  public
    property TestNumber: integer write FTestNumber;
  end;
 
implementation
 
uses SysUtils, Dialogs, PrimeForm, Windows;
 
procedure TPrimeThrd.UpdateResults;
begin
  PrimeFrm.ResultsMemo.Lines.Add(FResultString);
end;
 
function TPrimeThrd.IsPrime: boolean;
 
var
  iter: integer;
 
begin
  result := true;
  if FTestNumber < then
  begin
    result := false;
    exit;
  end;
  if FTestNumber <= then
    exit;
  iter := 2;
  while (iter < FTestNumber) and (not terminated) do {Line A}
  begin
    if (FTestNumber mod iter) = then
    begin
      result := false;
      {exit;}
    end;
    Inc(iter);
  end;
end;
 
procedure TPrimeThrd.Execute;
begin
  if IsPrime then
    FResultString := IntToStr(FTestNumber) + ' is prime.'
  else
    FResultString := IntToStr(FTestNumber) + ' is not prime.';
  if not Terminated then {Line B}
  begin
    Synchronize(UpdateResults);
    PostMessage(PrimeFrm.Handle, WM_THREAD_COMPLETE, 00);
  end;
end;
 
end.


В нем тоже есть небольшие отличия от того, что было в Главе 3:

  • Функция IsPrime теперь проверяет запросы на завершение потока, обеспечивая быстрый выход, если установлено свойство Terminated.
  • Процедура Execute делает проверку на нештатное завершение.
  • При нормальном завершении используется Synchronize для показа результатов и главной форме посылается сообщение - запрос на освобождение потока.



Введение в обработку сообщений и отложенное уведомление

При нормальном ходе дел поток выполняется, использует Synchronize для показа результатов, а затем посылает сообщение главной форме. Это сообщение асинхронно: главная форма получит его в некоторый момент чуть позже. PostMessage не приостанавливает рабочий поток, он продолжает свою работу до завершения. Это очень полезное свойство: мы не можем использовать Synchronize для того, чтобы сообщить главной форме, что пора освободить поток, потому что тогда мы бы "вернулись" из вызова Synchroni… Продолжение »

Сделать бесплатный сайт с uCoz