среда, 3 августа 2011 г.

Знакомство с потоками в Delphi

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

Один из таких этапов - Проргаммирование и используемые в нем потоки.
 Потоки используют для выполнения псевдопаралельных процессов. Это если в одну единицу времени нужно выполнять различные задачи.
 (Например, одновременная загрузка содержимого из двух очень больших файлов в 2 компонента TMemo)

Я решил показать максимально простой примитив, на который оказался способен. Я не буду загружать файлы, а использую 2 компонента TProgressBar и одну кнопку TButton
(для тех кто в танке - Button - Вкладка Standart - ProgressBar - вкладка Win32)

Моя программа будет написана только в одном окне Unit1 и паралельно загружать оба ProgressBar а.
 Так сделано потомучто статья содержит ознакомительный контент и должна дать понять как использовать потоки.

Подготавливаем форму
Помещаем 2 компонента ProgressBar на нашу форму. Делаем их одинаковых размеров.
  1. Помещаем 1 кнопку Button на форму
  2. Нажимаем ctrl+F12 (либо View -> Units)
Приступаем к написанию кода. Начинаем программирование и создаем свои потоки))

Для удобства воспринятия следующего кода добавим одну константу.
const N=12000; - Это будет константа для расширения положений ползунка.

========================================================
 Определяем новый класс и определяем ему тип Потоки (TThread)

Type 
    MyThread = class(TThread)
end;

Написать это нужно сразу после определения типа TForm (начинается Type Form=class(TForm) - заканчивается оператором end; )

Наши потоки будут использовать 2 ProgressBar, значит создадим 2 процедуры. Это те 2 процедуры, которые должны выполняться параллеьно. Вписываем их перед словом Private.

Button1: TButton;
    procedure _1;
    procedure _2;    
  private

Когда написали эти процедуры - можно нажать ctrl+shift+C - в наш код, в нужные места должны поставиться такие строчки:

procedure TForm1._1;
begin

end;

procedure TForm1._2;
begin

end;

Сюда мы впишем наш код. Первая процедура - увеличивает ползунок Первого ProgressBar,
вторая ползунок второго

procedure TForm1._1;
begin
 ProgressBar1.Position:=ProgressBar1.Position+1; //Смещаем ползунок
end;

procedure TForm1._2;
begin
 ProgressBar1.Position:=ProgressBar1.Position+1;;//Смещаем ползунок
end;

- Так мы написали 2 отдельно выполняемых фрагмента кода. Теперь нужно знать как их вызвать и заставить работать в параллельном режиме, другими словами - это наши потоки. Если сейчас выполнить программу - то загрузится форма и ничего не произойдет.

Добавляем к нашему классу потоки  (NewThread) новую процедуру. 

Type
 NewThread = class(TThread)
 protected
     procedure execute;override;
  end;

Нажимаем Ctrl+shift+C - в код должны вставиться такое:
procedure NewThread.execute;
begin
  inherited;

end;

Это та процедура, которая будет отвечать за то, что наши потоки будут выполняться параллельно. С помощью неё мы и будем потоки эти синхронизировать. Для синхронизации добавляем новую переменную в кусок кода (делаем её видимой)

 Type
 NewThread = class(TThread)
 protected
     procedure execute;override;
public 
    W:Boolean
  end;

  Теперь пишем сам кусок кода, синхронизирующий потоки. Для этого в кусок кода, отвечающий за синхронизаци пишем:

procedure NewThread.execute;
begin
  inherited;
     While (Form1.ProgressBar1.Position<N) or (Form1.ProgressBar2.Position<N) do Begin
        if  W=true then Synchronize(Form1._1)
        else Synchronize(Form1._2);
     end;
end;


Это один из методов синхронизации, он годится только для простых  проектов. Осталось только его вызвать. Вызывать наши потоки, мы будем нажатием на кнопку. Для этого переходим на форму (Shift+F12 или view-Forms) и делаем двойной клик на кнопке. Должна появиться новая процедура.

 procedure TForm1.Button1Click(Sender: TObject);
begin

end;

Добавляем 2 переменные, соответствующие потокам

procedure TForm1.Button1Click(Sender: TObject);
var P1,P2:NewThread;
begin

end;

Теперь пишем самый главный код - Инициализируем и запускаем наши потоки.

procedure TForm1.Button1Click(Sender: TObject);
var P1,P2:NewThread;
begin
   P1:=NewThread.Create(true); P2:=NewThreadCreate(true); //Инициализировали потоки

   P1.W:=true;P2.W:=false; // Установили значения для проверки
  
{Установили приоритеты выполнения}
  P1.Priority:=tplowest;
  P1.Priority:=tphighest;

   P1.Resume;  P2.Resume;  (Продолжаем выполнение потоков)
end;

Все, потоки готовы, но есть маленький момент. Чтобы они не подвесили программу наподобии бесконечного цикла, мы должны их перевести в спящее положение. У нас, в нашем примере это сделать очень просто. Можно, например, в обоих процедурах указать условие, что если Ползунок достигает конца, то перейти в бездействие.

procedure TForm1._1;
begin
 if ProgressBar1.Position<Form1.ProgressBar1.Position then  ProgressBar1.Position:=ProgressBar1.Position+1;
end;

procedure TForm1._2;
begin
   if ProgressBar2.Position<Form1.ProgressBar2.Position then ProgressBar2.Position:=ProgressBar2.Position+1;
end;

==================================================
Код программы: 

 unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    ProgressBar2: TProgressBar;
    Button1: TButton;
    procedure _1;
    procedure _2;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }

  end;

Type
 NewThread = class(TThread)
 protected
     procedure execute;override;
     public W:boolean;
  end;
var
  Form1: TForm1;
  i:Integer;

  const N=12000;
implementation

{$R *.dfm}

{ NewThread }


{ TForm1 }

procedure TForm1._1;
begin
  if ProgressBar1.Position<N then ProgressBar1.Position:=ProgressBar1.Position+1;
end;

procedure TForm1._2;
begin
   if ProgressBar2.Position<N then ProgressBar2.Position:=ProgressBar2.Position+1;
end;

{ NewThread }

procedure NewThread.execute;
begin
  inherited;
     While (Form1.ProgressBar1.Position<N) or (Form1.ProgressBar2.Position<N) do Begin
        if  W=true then Synchronize(Form1._1)
        else Synchronize(Form1._2);
     end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var P1,P2:NewThread;
begin
ProgressBar1.Max:=N;
ProgressBar2.Max:=N;

   P1:=NewThread.Create(true);
   P2:=NewThread.Create(true);

   P1.W:=true;
   P2.W:=false;

   P1.Priority:=tplowest;
   P2.Priority:=tphighest;

   P1.Resume;
   P2.Resume;
end;

end.