Главная » Delphi » Многопоточная графика

0

Ранее  уже отмечалось, что компоненты библиотеки VCL не предназначены для одновременной работы с несколькими потоками, однако  это  не совсем  так. Подпро граммы  библиотеки VCL способны поддерживать многопоточный режим  для отдель ных  графических объектов. Благодаря новым  методам  Lock() и  Unlock() класса TCanvas был создан  целый  модуль Graphics, который обеспечивает поддержку  мно гопоточности. Он включает в себя такие  классы,  как TCanvas, TPen, TBrush, TFont, TBitmap, TMetafile, TPicture и TIcon.

НА ЗАМЕТКУ

Любое окно графического интерфейса Windows (не только высокого уровня, но и обыч- ные кнопки), обладает двумя обязательными элементами: границей (border) и поверхно- стью (canvas). Окна высокого уровня могут иметь заголовок, содержащий название окна и кнопки управления. Таким образом, вся поверхность любого окна, за исключением границ и заголовка, представляет собой объект Canvas, на котором прорисовано все содержи- мое окна. В переводе с английского canvas означает “холст” или “канва”, а поскольку по- верхность создаваемой формы в окне конструктора форм покрыта маркерами разметки и напоминает ткань, ее называли “тканью”.

Программная реализация методов Lock() имеет  элементы, похожие и на крити ческие секции, и на функцию API EnterCriticalSection() (описанную в этой  гла ве ранее). Они  предназначены для защиты доступа  к объекту  Canvas или  графиче ским объектам. После того как конкретный поток вызовет метод Lock(), ему предос тавляется право  монополного владения объектом Canvas или графическими объекта ми. Другие  потоки, ожидающие выполнения кода,  расположенного за обращением к методу Lock(), будут переведены в состояние ожидания. Оно  продлится до тех пор, пока  поток, владеющий критическим разделом, не вызовет метод  Unlock(). Чтобы освободить критический раздел  и разрешить следующему ожидающему  потоку  (если таковой имеется) выполнить защищенную  часть  кода,  метод  Unlock() вызывает, в свою  очередь, функцию  LeaveCriticalSection(). На  примере следующих  строк показано, как использовать эти методы  для управления доступом к объекту Canvas:

Form.Canvas.Lock;

// здесь находится код, манипулирующий объектом Canvas

Form.Canvas.Unlock;

В листинге 5.13 представлен модуль Main проекта MTGraph — приложения, в кото ром демонстрируется доступ нескольких потоков к поверхности формы (объекту Canvas).

Листинг 5.13. Модуль Main.pas проекта MTGraph

unit Main; interface uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

Menus;

type

TMainForm = class(TForm)

MainMenu1: TMainMenu;

Options1: TMenuItem;

AddThread: TMenuItem;

RemoveThread: TMenuItem;

ColorDialog1: TColorDialog;

Add10: TMenuItem;

RemoveAll: TMenuItem;

procedure FormCreate(Sender: TObject);

procedure FormDestroy(Sender: TObject);

procedure AddThreadClick(Sender: TObject);

procedure RemoveThreadClick(Sender: TObject);

procedure Add10Click(Sender: TObject);

procedure RemoveAllClick(Sender: TObject);

private

ThreadList: TList;

public

{ Открытые объявления }

end;

TDrawThread = class(TThread)

private

FColor: TColor;

FForm: TForm;public

constructor Create(AForm: TForm; AColor: TColor);

procedure Execute; override;

end;

var

MainForm: TMainForm;

implementation

{$R *.DFM}

{ TDrawThread }

constructor TDrawThread.Create(AForm: TForm; AColor: TColor);

begin

FColor := AColor;

FForm := AForm;

inherited Create(False);

end;

procedure TDrawThread.Execute;

var

P1, P2: TPoint;

procedure GetRandCoords;

var

MaxX, MaxY: Integer;

begin

// Инициализировать точки P1 и P2 случайными координатами

// в пределах границ формы

MaxX := FForm.ClientWidth;

MaxY := FForm.ClientHeight;

P1.x := Random(MaxX);

P2.x := Random(MaxX);

P1.y := Random(MaxY);

P2.y := Random(MaxY);

end;

begin

FreeOnTerminate := True;

// Поток выполняется, пока он или приложение не будут завершены

while not (Terminated or Application.Terminated) do begin

GetRandCoords;                 // инициализировать P1 и P2

with FForm.Canvas do begin

Lock;                          // Блокировать объект Canvas

// только один поток может выполнять следующий код:

Pen.Color := FColor;           // установить цвет пера

MoveTo(P1.X, P1.Y);            // переход к точке P1

LineTo(P2.X, P2.Y);            // нарисовать линию до точки P2

// после выполнения следующий строки другому потоку будут

// разрешено войти в вышеупомянутый блок кода

Unlock;                        // Разблокировать объект Canvas

end;end;

end;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);

begin

ThreadList:= TList.Create;

end;

procedure TMainForm.FormDestroy(Sender: TObject);

begin

RemoveAllClick(nil);

ThreadList.Free;

end;

procedure TMainForm.AddThreadClick(Sender: TObject);

begin

// Добавить новый поток в список и разрешить пользователю

// выбрать цвет

if ColorDialog1.Execute then

ThreadList.Add(TDrawThread.Create(Self, ColorDialog1.Color));

end;

procedure TMainForm.RemoveThreadClick(Sender: TObject);

begin

// завершить последний поток в списке и удалить его

TDrawThread(ThreadList[ThreadList.Count – 1]).Terminate;

ThreadList.Delete(ThreadList.Count – 1);

end;

procedure TMainForm.Add10Click(Sender: TObject);

var

i: Integer;

begin

// создать 10 потоков, каждый с произвольным цветом

for i := 1 to 10 do

ThreadList.Add(TDrawThread.Create(Self, Random(MaxInt)));

end;

procedure TMainForm.RemoveAllClick(Sender: TObject);

var

i: Integer;

begin

Cursor := crHourGlass;

try

for i := ThreadList.Count – 1 downto 0 do begin

TDrawThread(ThreadList[i]).Terminate; // завершить поток

TDrawThread(ThreadList[i]).WaitFor;          // Убедиться, что

end;                                         // поток завершился.

ThreadList.Clear;

finally

Cursor:= crDefault;end;

end;

initialization

Randomize;       // инициализация генератора случайных чисел

end.В  этом  приложении  предусмотрено главное меню,   содержащее четыре  пункта (рис. 5.10). Пункт Add Thread (Добавить поток) создает  новый экземпляр класса TDrawThread, который рисует прямые линии, случайным образом расположенные на главной форме. Этот пункт можно  выбирать снова  и снова,  вливая  тем самым свежие струи  в коктейль потоков, уже получивших доступ  к поверхности главной формы. При  выборе следующего  пункта,  Remove Thread (Удалить  поток), удаляется  поток, который был  добавлен последним. С помощью третьей команды, Add 10 (Добавить

10),  создается десять  новых  экземпляров TDrawThread. И,  наконец, четвертая ко манда,  Remove All (Удалить  все),  завершает и удаляет  все экземпляры TDrawThread. На рис. 5.10 также  показан результат работы десяти  потоков, которые одновременно рисуют на поверхности формы.

Рис. 5.10. Главная форма проекта MTGraph

Правило блокировки объекта Canvas гласит, что  каждый  пользователь будет бло кировать его перед  началом рисования и отменять блокировку по окончании. Таким образом потоки не будут мешать друг другу. Обратите внимание: все события OnPaint и вызовы метода  Paint(), инициированные VCL, автоматически блокируют и отме няют блокировку объекта Canvas автоматически, поэтому  обычный код Delphi  может сосуществовать с графическими операциями новых  фоновых потоков.

Используя это  приложение в качестве примера, рассмотрим сценарий возможного конфликта потоков, возникающего при  невыполнении блокировки объекта Canvas. Если поток 1 устанавливает красный цвет пера и рисует прямую, а поток 2 устанавливаетсиний цвет пера и рисует окружность, причем эти потоки не блокируют объект Canvas перед  началом выполнения своих  операций, то  возможен следующий  сценарий кон фликта потоков. Поток 1 установит красный цвет пера.  Планировщик операционной систе мы (OS scheduler) передаст управление потоку 2. Поток 2 установит синий цвет  пера  и нарисует окружность. Эстафета выполнения кода снова  переходит к потоку 1. Поток 1 рисует прямую. Но эта прямая оказывается не красной, а синей, поскольку  потоку 2 уда лось “втиснуться” со своей установкой синего цвета между операциями потока 1.

Заметим, что  для  возникновения подобных проблем достаточно иметь  хотя  бы один  поток нарушитель. Если поток  1 будет исправно блокировать объект Canvas, а поток  2 —  нет,  то только  что  описанного сценария избежать не удастся. Для предот вращения конфликта потоков необходимо, чтобы  оба потока блокировали объект Canvas на время  выполнения операций над ним.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии