Thread (Kanal) nedir? Delphi’de Thread kullanımı örnekleri

Thread uygulaması thread çalışırken

29.10.2017 tarihinde Bir Thread’in Diğer Thread’i Beklemesi konusu ve yeni bir örnek eklenmiştir. 26.11.2017 tarihinde konu ve örnek değiştirilmiştir.

14.10.2017 tarihinde Thread’de PostMessage kullanımı konusu ve yeni bir örnek eklenmiştir

Örnek kod 29.09.2017 tarihinde değiştirilmiştir. Eski kodlar karşılaştırma yapılabilmesi için silinmemiş; yorum haline getirilip ** karakterleri de eklenerek saklanmıştır

Windows’da her bir uygulama çalıştırıldığında artık windows için birer işlem (process) olur. Bir işlem Windows için fazla bir şey ifade etmez. Çünkü işlemler sadece hafızada belli bir bölgede var olmaktan sorumludur. Esas işlemi yapan kısım kanallardır (thread). Her işlem en az bir adet kanala sahiptir. Win 3.1 gibi işletim sistemleri sadece bir adet kanala sahiptir. Windows 95 ve üstü, Unix, OSX gibi işletim sistemleri birden fazla kanala sahiptirler.

Windows bir programı ya da bir DLL’i ilk başta işlem olarak hafızaya taşır. Bu esnada işlem eylemsiz olarak durur. Bu işleme ait kanallar ise belirlenen ölçülerde programın kodlarını çalıştırmaya başlar. Her kanalın windows tarafından atanmış bir işletilme süresi vardır. Bu quanta olarak isimlendirilir. Windows sistemleri 20 ms’lik quanta sistemi kullanır. Bir programın bırakılarak diğer bir programın çalıştırılması işlemine görevler arası geçiş (task switch) denir. Her programın 0-31 arasında bir öncelik derecesi vardır. Sistem en yüksek öncelikteki programlar arasında değiş tokuş yapar. Ancak onlar bittikten sonra diğerleriyle uğraşılır. Böylece tüm programlar aynı anda çalışıyor gibi görünür.

Her bir kanal sahip olduğu önceliğe göre işletim sistemi tarafından işleme alınacaktır. Bu işleme alış esnasında sahip olduğu context’deki register değerleri yüklenir. Esp, Ebp, Eip gibi registerlar da yüklendiğinden, kanal önceden kaldığı yerden önceki stack değerlerine göre işlemine devam edecektir. Ve yine önceliğine göre belli bir süre sonunda işletim sistemi bu kanalı durdurup register değerlerini context yapısında saklar. Daha sonra diğer bir kanalın context verisini yükleyerek başka bir kanalı işlemeye geçer. Ve bu işlem tüm kanallar için devam edip gider. İşletilme ve geçiş süreleri çok kısa olduğundan, sanki aynı anda tüm kanallar işletiliyormuş gibi görünecektir. Halbuki bütün kanallar belli bir sıra ile çok kısa sürelerde işletilip diğer bir kanala geçilmektedir. Yani her kanal aynı anda çalışmaz, kullanıcı öyle zanneder. Tabi bu anlatılanlar tek işlemci ve tek çekirdek için geçerlidir.

Kanalların işletilme sürelerini ise kanalların önceliği belirlemektedir. Hangi kanal daha yüksek öncelikli ise o kanal daha fazla işletilme süresine sahip olur. Eğer kanal arkaplanda çalışma üzere ayarlanmış ise, diğer kanallar işlemlerini tamamladıktan sonra ancak bu kanala sıra gelecektir. Eğer kanal çok yüksek önceliğe sahip ise uzun bir süre bu kanal işleme devam edecek ve diğer kanallara sıra gelmeyecektir. Ama uzun süre sıra gelmeyen düşük öncelikli kanallarda belli düzene göre geçici olarak önceliği yükseltilerek çalıştırılır. Durdurulmuş kanallar dikkate alınmaz.

Her program bir kanala ana kanal (main thread) sahiptir fakat bir program ile birden fazla kanal açılıp kullanılabilir. Bu şekilde kullanımın amacı yazılan programın kendi işlemlerinin yanında sürekli başka işlemlerde yapabilmesini sağlamaktır. Yazılan programda birbirine paralel yapılmasını istenen birden fazla işlem varsa her işlem için kanal kullanılır. Örneğin bir program sürekli internet işlemleri yapıyorsa kendi işlemleri için bir kanal, internet işlemleri için başka bir kanal kullanabilir. Böylece program internetten dosya indirme veya yükleme sırasında bir kanalı meşgulken ana kanalda kendi işlemlerini yapar ve bloke olmaz. Ayrıca bu yöntem performans artışı da sağlanmaktır. Her kanal ayrı bir programmış gibi windows tarafından çalıştırıldığından özellikle birden çok çekirdeği bulunduğu sistemlerde hız ciddi biçimde artar.

Çok çekirdekli sistemlerde aynı programın farklı kanalları aynı anda çalışabilir. Bu sebeple program içerisinde bulunan ortak kullanılan kaynakların (değişken, dosya vb.) kullanımına dikkat edilmelidir. Bir kanal bir kaynağı kullanırken programın diğer bir kanalı aynı kaynağa müdahale edebilir. Bu sebeple kontrol mekanizmaları eklenmiştir. Örneğin bir kanalın başlaması için diğer kanaldaki işlemin bitmesini beklenebilir; Bir kanala diğer kanaldaki işlem bitene kadar ara verilebilir; Bir kanaldaki işlem tam bitmeden ara verilmemesi sağlanabilir; Bir kanaldan sadece bir tane veya belirli sayıda açılması sağlanabilir.

Delphide Thread Kullanımı

Normalde bir kanal windows ortamında CreateThread Windows API’si ile oluşturulur. Fakat Delphi bize TThread isminde bir sınıf da sunmuştur. Eğer kanalda yapılacak işlemler VCL sınıflarını etkilemesi gerekiyorsa TThread sınıfını kanal oluşturmak için kullanılır. Yok eğer VCL sınıflarını ilgilendiren bir işlem söz konusu değilse doğrudan CreateThread API’si veya bu API’yi çağıran BeginThread komutu kullanılır.

Her kanal, çalışma esnasında kendi eax, ebx, edx gibi register verilerini tutan bir yapıya sahiptir. Bu yapıya context (içerik) adı verilmektedir. Bu yapının Delphi’deki karşılığı TContext olup, Windows unitindedir. Bu record tipini incelendiğinde bir çok register’ı barındırdığını görülecektir.

Delphide Thread Uygulaması

Aşağıdaki TThread sınıfı kullanılan basit bir thread örneği bulunmaktadır. Uygulamada Thread baslat butonu ile basit bir thread oluşturulmakta, Thread ara ver butonu ile thread’a ara verilip Suspend moduna alınmakta; Thread devam et butonu ile ara verilmiş thread Resume edilip yeniden çalıştırılmakta ve Thread bitir butonu ile thread’ın işi bitmeden zorla sonlandırılması Terminate edilmesi sağlanmaktadır. Normalde Thread bitir butonuna basılmadan da Thread işini bitirip otomatik sonlanmaktadır. Thread sonlandığında veya zorla sonlandırıldığında ayarlanmış Olay çağrılarak “Thread bitti!” mesajı gösterilmektedir. Mesaj kapatıldığında thread otomatik sonlanmaktadır.

Uygulama için önce normal form oluşturmak gereklidir. Bunun için File->New->Application ile boş bir uygulama oluşturulur. Bu uygulamada 4 adet TBitBtn, 1 adet TProgressBar ve 1 adet TXPManifest (TXPManifest sadece görünüm içindir. Uygulama için mecburi değildir, silinebilir) vardır. TBitBtn’lar sırasıyla Thread baslat, Thread ara ver, Thread devam et ve Thread bitir olarak isimlendirilir. Formun tasarımı aşağıdadır:

Thread uygulaması tasarımı

Form ve bileşenlerin Properties’deki Tasarım Anı ayarları (dfm dosyası) aşağıdaki şekildedir :

object Form1: TForm1
  Left = 384
  Top = 220
  Width = 466
  Height = 196
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object ProgressBar1: TProgressBar
    Left = 24
    Top = 32
    Width = 401
    Height = 17
    TabOrder = 0
  end
  object BitBtn1: TBitBtn
    Left = 24
    Top = 80
    Width = 81
    Height = 25
    Caption = 'Thread baslat'
    TabOrder = 1
    OnClick = BitBtn1Click
  end
  object BitBtn2: TBitBtn
    Left = 120
    Top = 80
    Width = 89
    Height = 25
    Caption = 'Thread ara ver'
    TabOrder = 2
    OnClick = BitBtn2Click
  end
  object BitBtn3: TBitBtn
    Left = 224
    Top = 80
    Width = 97
    Height = 25
    Caption = 'Thread devam et'
    TabOrder = 3
    OnClick = BitBtn3Click
  end
  object BitBtn4: TBitBtn
    Left = 336
    Top = 80
    Width = 89
    Height = 25
    Caption = 'Thread Bitir'
    TabOrder = 4
    OnClick = BitBtn4Click
  end
  object XPManifest1: TXPManifest
    Left = 280
    Top = 112
  end
end

Thread unit oluşturmak için File->New->Other->Thread Object seçilir. Gelen dialoga isim girilir (Uygulamada ismi Threadim). Oluşturulan Unit’te verilen isimle TThread sınıfından oluşturulmuş temel bir thread uniti otomatik oluşacaktır. Aşağıda otomatik oluşmuş temel thread uniti görülmektedir:

unit Unit2;

interface

uses
  Classes;

type
  Threadim = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;

implementation

{ Important: Methods and properties of objects in visual components can only be
  used in a method called using Synchronize, for example,

      Synchronize(UpdateCaption);

  and UpdateCaption could look like,

    procedure Threadim.UpdateCaption;
    begin
      Form1.Caption := 'Updated in a thread';
    end; }

{ Threadim }

procedure Threadim.Execute;
begin
  { Place thread code here }
end;

end.

Aşağıda bitmiş uygulamanın çalışma şekilleri ve dosya içerikleri bulunmaktadır :

Uygulama ilk çalıştırıldığında :

Thread uygulaması başlangıcı

Thread başlatılıp çalışırken (Ara verildiğinde ProgressBar ilerlemez) :

Thread uygulaması thread çalışırken

Thread sonlandığında veya sonlandırıldığında :

Thread uygulaması thread sonlandığında

Uygulamada 1 adet form ve 2 adet unit bulunmaktadır.

Uygulamanın formu (Unit1.pas) :

unit Unit1;

interface

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

type
  TThreadDurum = (tdYok, tdCalisiyor, tdDurdu);
 
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    BitBtn3: TBitBtn;
    BitBtn4: TBitBtn;
    XPManifest1: TXPManifest;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure BitBtn3Click(Sender: TObject);
    procedure BitBtn4Click(Sender: TObject);
  private
    { Private declarations }
    procedure threadbitti(sender: Tobject);
  public
    { Public declarations }
  end;

var
  Form1 : TForm1;
  Yenithread : Threadim;
  Threaddurum : TThreadDurum = tdYok;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject); // Thread Başlat
begin
  if Threaddurum <> tdYok then Exit;
  
  // ** Yenithread := Threadim.Create(threadbitti);
  Yenithread := Threadim.Create;
  // Thread oluşturuluyor

  Yenithread.Priority := tpLowest;
  // Thread önceliği
  // tpIdle = Çok düşük
  // tpLowest = Düşük
  // tpNormal = Normal
  // tpHighest = Yüksek
  // tpTimeCritical = Kritik yüksek
  
  Yenithread.OnTerminate := threadbitti;
  // Thread bittiğinde çağrılacak fonksiyon
 
  Yenithread.Resume; // Thread başlatılıyor
  // Delphi XE sürümünden sonra 
  // Thread başlatılırken Resume
  // yerine Start komutu kulanılmaktadır
  // Yenithread.Start;

  Threaddurum := tdCalisiyor;
end;

procedure TForm1.threadbitti(sender: Tobject); // Thread Bitti olayı 
begin
 showmessage('Thread bitti!');
 Threaddurum := tdYok;
end;

procedure TForm1.BitBtn2Click(Sender: TObject); // Thread Ara ver
begin
 if Threaddurum <> tdCalisiyor then Exit;
 Yenithread.Suspend;
 Threaddurum := tdDurdu;
end;

procedure TForm1.BitBtn3Click(Sender: TObject); // Thread Devam et
begin
 if Threaddurum <> tdDurdu then Exit;
 Yenithread.Resume;
 Threaddurum := tdCalisiyor;
end;

procedure TForm1.BitBtn4Click(Sender: TObject);  // Thread Durdur
begin
  if Threaddurum <> tdCalisiyor then Exit;
  Yenithread.Terminate;
end;

end.

Değiştirilmiş Thread Unitin son hali (Unit2.pas) :

unit Unit2;

interface

uses Classes, SysUtils;

type
  Threadim = class(TThread)
  private
    say : integer;
    procedure Ilerlet;
  protected
    procedure Execute; override;
  public
    // ** constructor Create(bittiolayi : TNotifyEvent);
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses Unit1;

{ ** constructor Threadim.Create(bittiolayi : TNotifyEvent); // Threadı oluşturuluyor
begin
  inherited Create(True);
  // True = Thread otomatik başlamasın, Resume Metodunu Beklesin
  // False = Thread otomatik başlasın
  FreeOnTerminate := True;
  // Thread Terminate Edildiğinde Class Free Olsun
  OnTerminate := bittiolayi;
  // Thread bittiğinde çağrılacak fonksiyon
  
  // Thread olayı parametresi olmadan da
  // Constructor Threadim.Create;
  // şeklinde de veya başka parametrelerle de
  // tanımlanabilir
  Resume;  // Thread başlatılıyor
end;}

constructor Threadim.Create; // Threadı oluşturuluyor
begin
  inherited Create(True);
  // True = Thread otomatik başlamasın, Resume Metodunu Beklesin
  // False = Thread otomatik başlasın
  FreeOnTerminate := True;
  // Thread Terminate Edildiğinde Class Free Olsun
end;

procedure Threadim.Execute; // Threadın sürekli çalışan kodu
begin
  say := 0;
  While ((say < 100) and not(Terminated)) do begin
   // Sürekli işin bitip bitmediği veya
   // Threadın sonlanıp sonlandırılmadığı
   // Kontrol ediliyor
   Inc(say);
   Synchronize(Ilerlet);
   // Ana formdaki bileşenlere ulaşmak
   // için Synchronize kullanılır
   // Parametre verilen fonksiyon çağrılır
   Sleep(150);
  end;
end;

destructor Threadim.Destroy; // Thread sonlandırılıyor
begin
 Inherited;
end;

procedure Threadim.Ilerlet; // Synchronize fonksiyonu
begin
 Form1.ProgressBar1.position := say;
 // Formlardaki bileşenlere ulaşırken
 // Synchronize ile bu fonksiyon çağrılmaktadır
 // Bu fonksiyon çalışırken thread işini
 // bırakıp kontrolü Ana threada bırakmaktadır
 // Bu sebepten buradaki işlemler
 // az olmalıdır
 
 // Synchronize yerine PostMessage veya
 // SendMessage Apileri ile Ana forma
 // mesaj gönderilebilir.
 // Böylece kontrolün Ana thread'a 
 // bırakılması gerekmez
end;

end.

Uygulama basit bir uygulamadır. Thread sadece ProgressBar bileşenini ilerletmektedir.

Thread’de PostMessage kullanımı

Delphi’de bir Thread’den kendi Uygulamasının bir Formundaki bileşenlere ulaşması istediğinde Synchronize metodu kullanılmaktadır. Synchronize sırasında Synchronize fonksiyonundaki işlemler bitene kadar Thread işini bırakıp kontrolü Ana Thread’a bırakmaktadır. Bu Thread’ın amacıyla çelişmektedir. Çünkü Thread kullanmanın amacı işin Ana thread’a yaptırılmamasıdır. Bu sebepten Synchronize fonksiyonundaki işlemler kısa tutulur.

Synchronize kullanmadan da Formdaki bileşenlere ulaşmak mümkündür. Bunu için bir kullanıcı mesajı tanımlanarak PostMessage Apisi ile o mesajın Thread’dan gönderilmesi ve Ana Thread tarafından Asenkron biçimde alınarak değerlendirilmesi metodu kullanılır. Bu kullanım asenkrondur. Çünkü Thread mesajı gönderir ve beklemeden işine devam eder. Ana Thread çalışma sırası geldiğinde mesaj kuyruğundaki sıradaki mesajları alır. Yoğunluğa göre Thread’in gönderdiği mesaja sıra gelmeyebilir. Thread’in mesajını alırsa işlemi yapar alamazsa sonraki çalışma sıralarından birinde mesajı alacaktır. Yani mesaj sırası yoğunluğuna göre bekleme olabilir.

Windows’ta herşey işlem mesaj ile yapılmaktadır. Mesajlar tüm uygulamalara gönderilir; tanımlanan mesaj da tüm uygulamalara gönderilecektir. Fakat tanımlanan mesaj uygulamaya özgü (kullanıcı mesajı) olduğu için diğer uygulamalar tarafından tanınmayacak ve dikkate alınmayacaktır.

Aşağıda Kullanıcı mesajı tanımlama, Thread’den PostMessage ile tanımlanan mesajı gönderme ve Ana Thread’den mesajı alıp değerlendirme uygulaması bulunmaktadır. Uygulamada say değişkeni değeri ve değişkenin stringe çevirilmiş hali Ana thread’a parametre olarak gönderilmekte ve string uygulama başlık çubuğunda da yazdırılmaktadır. Uygulama yukarıdaki uygulamaya gibidir fakat bu küçük farklılık vardır.

Thread PostMessage uygulaması tasarımı :

Thread Postmessage uygulaması tasarımı

Form ve bileşenlerin Properties’deki Tasarım Anı ayarları (dfm dosyası) aşağıdaki şekildedir :

object Form1: TForm1
  Left = 384
  Top = 220
  Width = 466
  Height = 196
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnClose = FormClose
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object ProgressBar1: TProgressBar
    Left = 24
    Top = 32
    Width = 401
    Height = 17
    TabOrder = 0
  end
  object BitBtn1: TBitBtn
    Left = 24
    Top = 80
    Width = 81
    Height = 25
    Caption = 'Thread baslat'
    TabOrder = 1
    OnClick = BitBtn1Click
  end
  object BitBtn2: TBitBtn
    Left = 120
    Top = 80
    Width = 89
    Height = 25
    Caption = 'Thread ara ver'
    TabOrder = 2
    OnClick = BitBtn2Click
  end
  object BitBtn3: TBitBtn
    Left = 224
    Top = 80
    Width = 97
    Height = 25
    Caption = 'Thread devam et'
    TabOrder = 3
    OnClick = BitBtn3Click
  end
  object BitBtn4: TBitBtn
    Left = 336
    Top = 80
    Width = 89
    Height = 25
    Caption = 'Thread Bitir'
    TabOrder = 4
    OnClick = BitBtn4Click
  end
  object XPManifest1: TXPManifest
    Left = 280
    Top = 112
  end
end

Thread PostMessage uygulaması çalışırken (Ara verildiğinde ProgressBar ilerlemez) :

Thread Postmessage uygulaması thread çalışırken

Uygulamanın formu (Unit1.pas) :

unit Unit1;

interface

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

const
  WM_PBAR_DEGERI_MESAJIM = WM_USER + 195;
  // Kullanıcı mesajı tanımlanıyor

type
  PThreadParametresi = ^TThreadParametresi;
  TThreadParametresi = record
    yazi: string[5];
    sayi: Integer;
  end;
  // Thread'dan gönderilecek Bilgi
  // Yapısı ve onun Pointeri tanımlanıyor 

  TThreadDurum = (tdYok, tdCalisiyor, tdDurdu);
 
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    BitBtn3: TBitBtn;
    BitBtn4: TBitBtn;
    XPManifest1: TXPManifest;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure BitBtn3Click(Sender: TObject);
    procedure BitBtn4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    procedure threadbitti(sender: Tobject);
    procedure MesajAlicisi(var Msg: TMessage); message WM_PBAR_DEGERI_MESAJIM;
    // Mesaj prosedürü tanımlanıyor
    // Tanımlanmış kullanıcı mesajı geldiğinde
    // bu prosedüre yönlendirilecek
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Yenithread: Threadim;
  Threaddurum: TThreadDurum = tdYok;
  ThreadParametresi: PThreadParametresi;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject); // Başlat
begin
  if Threaddurum <> tdYok then Exit;
  Yenithread := Threadim.Create; 
  Yenithread.Priority := tpLowest;
  Yenithread.OnTerminate := threadbitti;
  Yenithread.Resume;  //Thread başlatılıyor
  Threaddurum := tdCalisiyor;
end;

procedure TForm1.threadbitti(sender: tobject);
begin
 showmessage('Thread bitti!');
 Threaddurum := tdYok;
end;

procedure TForm1.BitBtn2Click(Sender: TObject); // Ara ver
begin
 if Threaddurum <> tdCalisiyor then Exit;
 Yenithread.Suspend;
 Threaddurum := tdDurdu;
end;

procedure TForm1.BitBtn3Click(Sender: TObject); // Devam et
begin
 if Threaddurum <> tdDurdu then Exit;
 Yenithread.Resume;
 Threaddurum := tdCalisiyor;
end;

procedure TForm1.BitBtn4Click(Sender: TObject);  // Durdur
begin
  if Threaddurum <> tdCalisiyor then Exit;
  Yenithread.Terminate;
end;

procedure TForm1.MesajAlicisi(var Msg: TMessage);
var Tp : PThreadParametresi;
begin
   Tp := PThreadParametresi(Msg.WParam);
   Form1.Caption := Tp.yazi;
   ProgressBar1.position := Tp.sayi;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 New(ThreadParametresi);
 // Mesaj için bilgi saklanacak
 // yapı hafızda oluşturuluyor
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Dispose(ThreadParametresi); 
 // Mesaj için bilgi saklanan
 // yapı hafızdan siliniyor
end;

end.

Thread Uniti (Unit2.pas) :

unit Unit2;

interface

uses Classes, SysUtils, Windows;

type
  Threadim = class(TThread)
  private  { Private declarations }
   say : integer;
   // procedure Ilerlet;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses Unit1;

constructor Threadim.Create;
begin
  inherited Create(True);
  // True = Thread otomatik başlamasın, Resume Metodunu Beklesin
  // False = Thread otomatik başlasın
  FreeOnTerminate := True;
  // Thread Terminate Edildiğinde Class Free Olsun
end;

procedure Threadim.Execute;
begin
  say := 0;
  while ((say<100) and not(Terminated)) do begin
   Inc(say);
   // Synchronize(Ilerlet);
   ThreadParametresi^.yazi := IntToStr(say);
   ThreadParametresi^.sayi := say;
   PostMessage(Form1.Handle, WM_PBAR_DEGERI_MESAJIM, Integer(ThreadParametresi), 0);
   Sleep(150);
  end;
end;

destructor Threadim.Destroy;
begin
 inherited;
end;

{ procedure Threadim.Ilerlet;
Begin
 Form1.ProgressBar1.position := say;
end; }

end.

PostMessage ile 2 adet integer bilgi (Msg.WParam ve Msg.LParam) doğrudan gönderilebilir. String veya Record tipindeki bilgiler için ise yukarıdaki gibi bilginin hafızadaki adresi (Pointer) değeri gönderilir.

Bir Thread’in Diğer Thread’i Beklemesi

Aşağıdaki uygulama yukarıdaki uygulamaların değiştirilmiş halidir. Uygulamada butona tıklandığında 2 adet thread oluşturulmakta ve bu threadler çalıştırılmaktadır. Normalde iki thread tek ProgressBar’ı aynı anda değiştirmektedir. Aynı kaynağı birden fazla thread’in aynı anda kullanması sakıncalı bir durumdur. Hatalı sonuçlara neden olabilir. İkinci thread’in birinci thread’in işini bitirmesini beklemesi için kontrol mekanizması konulması gerekmektedir.

Thread’larda bir thread’in diğer thread’i beklemesinin sağlayan birçok yöntem vardır. Birinci yöntem Critical Section (Kritik Bölge) oluşturmaktır. Thread’in sürekli çalışan (Execute prosedürü) kodunun kritik (bölünmemesi) gereken kısmının başlangıçına EnterCriticalSection bitiş kısmına LeaveCriticalSection komutları eklenerek bölünemez bölge oluşturulur. Thread’a sıra gelip bu bölge çalıştırılırken bu bölgedeki kodlar bitene kadar sistem thread’e devam eder. Tabi bu bölge kısa tutulmalıdır. Bu yöntemi kullanabilmek için program başlangıçında InitializeCriticalSection komutu ile bölge değişkeni tanımlanmalı. Programdan çıkarken ise DeleteCriticalSection komutu ile bu değişken silinmelidir.

İkinci daha basit yöntem ise Mutex oluşturmaktır. Mutex programlarda kullanılan bir mekanizma ve ayrıca hafıza nesnesinin adıdır. Mutex nesnesi oluşturulduktan sonra sadece oluşturulan program içinde değil tüm sistem genelinde geçerlidir. Mutex programdan çıkılırken silinmelidir.

Mutex sadece thread’ler için değil diğer işlemler için de kullanılmaktadır. En çok yapılan Mutex uygulaması programın sadece bir defa çalışması istenen durumlarda ilk seferde Mutex’in oluşturulması ve girişte kontrol edilmesidir. Program ikinci çalıştırıldığında girişteki kontrolde Mutex’in zaten var olduğu görülecek uygulamanın içindeki kendi kendini kapatmasını sağlayan kodlar çalıştırılacaktır.

  // Uygulamanın Proje (dpr) dosyasına eklenir
  CreateMutex(nil, False, 'Dm7Blog');
  if GetLastError = ERROR_ALREADY_EXISTS then
  begin
    Application.MessageBox(PChar('Program zaten çalışıyor !'), PChar('Dm7Blog'), MB_OK);
    Halt(0); 
    // Uygulama 2. kopyası  mesaj verip
    // kendi kendini kapatır
  end;

Thread’da Mutex kullanımında ise sürekli çalışan (Execute prosedürü) kodun girişine önceden oluşturulmuş Mutex bekleme, kodun bitişine Mutex bırakma kodu eklenir. Oluşturulan ilk thread normal olarak çalışır. İkinci thread’de aynı Mutex’in bitmesini bekleyecek ve koda devam edemeyecektir. Ne zaman ilk thread işini bitirip oluşturduğu Mutex’i bırakırsa ikinci thread normal çalışmaya devam edecektir. Böylece ikinci thread birincinin bitmesini beklemiş olacaktır. Üçüncü bir thread olsaydı oda önce birinciyi sonra ikinciyi beklemek zorunda kalacaktı.

Bunu bankamatikte işlem yapmaya benzetmek mümkündür. Bankamatikte ikinci sıradaki kişi ve diğerleri işlem yapan kişiyi beklemek zorundadırlar. Ne zaman işlem yapan kişi işini bitirirse ikinciye ve diğer bekleyenlere sıra gelecektir.

Formun tasarımı aşağıdadır:

3. Thread uygulaması tasarımı

Uygulamanın Form ve bileşenlerin Properties’deki Tasarım Anı ayarları (dfm dosyası) aşağıdaki şekildedir :

object Form1: TForm1
  Left = 384
  Top = 220
  Width = 466
  Height = 156
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnClose = FormClose
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object ProgressBar1: TProgressBar
    Left = 24
    Top = 32
    Width = 401
    Height = 17
    TabOrder = 0
  end
  object BitBtn1: TBitBtn
    Left = 152
    Top = 64
    Width = 145
    Height = 25
    Caption = 'Threadlari baslat'
    TabOrder = 1
    OnClick = BitBtn1Click
  end
  object XPManifest1: TXPManifest
    Left = 336
    Top = 56
  end
end

Uygulama ilk çalıştırıldığında :

3. Thread uygulaması başlangıcı

1. Thread çalışırken:

1. Thread sonlandığında (2. Thread halen çalışıyor) :

3. Thread uygulaması thread sonlandığında

A. Uygulamanın Critical Section (Kritik Bölge) yöntemi ile yapılmış hali aşağıdadır

Uygulamada 1 adet form ve 2 adet unit bulunmaktadır.

Uygulamanın formu (Unit1.pas) :

unit Unit1;

interface

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

const
  WM_PBAR_DEGERI_MESAJIM = WM_USER + 195;
  // Kullanıcı mesajı tanımlanıyor

type
  PThreadParametresi = ^TThreadParametresi;
  TThreadParametresi = record
    Id: Integer;
    yazi: string[4];
    sayi: Integer;
  end;
  // Thread'dan gönderilecek Bilgi
  // Yapısı ve onun Pointeri tanımlanıyor 
 
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    BitBtn1: TBitBtn;
    XPManifest1: TXPManifest;
    procedure BitBtn1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    procedure threadbitti(sender: Tobject);
    procedure threadbitti2(sender: Tobject);
    procedure MesajAlicisi(var Msg: TMessage); message WM_PBAR_DEGERI_MESAJIM;
    // Mesaj prosedürü tanımlanıyor
    // Tanımlanmış kullanıcı mesajı geldiğinde
    // bu prosedüre yönlendirilecek
  public    { Public declarations }
  end;

var
  Form1: TForm1;
  Yenithread, YeniThread1: Threadim;
  ThreadParametresi: PThreadParametresi;
  KritikBolge: TRTLCriticalSection;
  Tp: PThreadParametresi;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject); //Başlat
begin
  Yenithread := Threadim.Create; // 1. Thread oluşturuluyor
  Yenithread.Priority := tpLowest;
  Yenithread.OnTerminate := threadbitti;
  Yenithread.Resume;  // 1. Thread başlatılıyor

  Yenithread1 := Threadim.Create; // 2. Thread oluşturuluyor
  Yenithread1.Priority := tpLowest;
  Yenithread1.OnTerminate := threadbitti2;
  Yenithread1.Resume; // 2. Thread başlatılıyor

  BitBtn1.Enabled := False;
  // Button pasif yapılıyor
  // Çünkü  her tıklamada farklı
  // 2 thread daha oluşacaktır
end;

procedure TForm1.threadbitti(sender: tobject);
begin
 Showmessage('1. Thread bitti !');
end;

procedure TForm1.threadbitti2(sender: tobject);
begin
 Showmessage('2. Thread bitti !');
end;

procedure TForm1.MesajAlicisi(var Msg: TMessage);
begin
 Tp := PThreadParametresi(Msg.WParam);
 Form1.Caption := tp.yazi + ' - Çalışan Thread Id : ' + Inttostr(tp.Id);
 ProgressBar1.position := tp.sayi;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 New(ThreadParametresi);
 // Mesaj için bilgi saklanacak
 // yapı hafızda oluşturuluyor
 InitializeCriticalSection(KritikBolge);
 // Yeni Critical Section oluşturuluyor
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Dispose(ThreadParametresi);
 // Mesaj için bilgi saklanan
 // yapı hafızdan siliniyor
 DeleteCriticalSection(KritikBolge);
 // Critical Section siliniyor
end;

end.

Değiştirilmiş Thread Unitin son hali (Unit2.pas) :

unit Unit2;

interface

uses Classes, SysUtils, Windows;

type
  Threadim = class(TThread)
  private  { Private declarations }
   say:integer;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses Unit1;

constructor Threadim.Create;
begin
  inherited Create(True);
  // True = Thread otomatik başlamasın, Resume Metodunu Beklesin
  // False = Thread otomatik başlasın
  FreeOnTerminate := True;
  // Thread Terminate Edildiğinde Class Free Olsun
end;

procedure Threadim.Execute;
begin
  EnterCriticalSection(KritikBolge);
  // Critical Section başlangıcı
  say := 0;
  while ((say<100) and not(Terminated)) do begin
   Inc(say);
   ThreadParametresi^.yazi := IntToStr(say);
   ThreadParametresi^.sayi := say;
   ThreadParametresi^.Id := ThreadID;
   PostMessage(Form1.Handle, WM_PBAR_DEGERI_MESAJIM, Integer(ThreadParametresi), 0);
   Sleep(150);
  end;
  LeaveCriticalSection(KritikBolge);
  // Critical Section sonu
end;

destructor Threadim.Destroy;
begin
 inherited;
end;

end.

Uygulamada bulunan Butona tıklandığında aynı sınıftan 2 adet Thread oluşturulur ve çalıştırılır. Thread sınıfına Critical Section kontrol mekanizması konulduğu için ilk çalışan thread normal çalışırken ikinci thread birincinin işini bitirip Critical Section’ı serbest bırakmasını bekler. Uygulama başlığında progressbar’ın durumunun yanında aynı zamanda o anki çalışan thread’in ThreadId’si yazmaktadır. Thread’ler sayı değişkenin yanında ThreadID’sini de parametre olarak göndermektedir. Birinci thread bittiğinde “1. Thread bitti !”, ikinci thread bittiğinde “2. Thread bitti !” mesajı çıkmaktadır.

B. Uygulamanın Mutex yöntemi ile yapılmış hali aşağıdadır

Uygulamada 1 adet form ve 2 adet unit bulunmaktadır.

Uygulamanın formu (Unit1.pas) :

unit Unit1;

interface

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

const
  WM_PBAR_DEGERI_MESAJIM = WM_USER + 195;
  // Kullanıcı mesajı tanımlanıyor

type
  PThreadParametresi = ^TThreadParametresi;
  TThreadParametresi = record
    Id: Integer;
    yazi: string[4];
    sayi: Integer;
  end;
  // Thread'dan gönderilecek Bilgi
  // Yapısı ve onun Pointeri tanımlanıyor 
 
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    BitBtn1: TBitBtn;
    XPManifest1: TXPManifest;
    procedure BitBtn1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    procedure threadbitti(sender: Tobject);
    procedure threadbitti2(sender: Tobject);
    procedure MesajAlicisi(var Msg: TMessage); message WM_PBAR_DEGERI_MESAJIM;
    // Mesaj prosedürü tanımlanıyor
    // Tanımlanmış kullanıcı mesajı geldiğinde
    // bu prosedüre yönlendirilecek
  public    { Public declarations }
  end;

var
  Form1: TForm1;
  Yenithread, YeniThread1: Threadim;
  ThreadParametresi: PThreadParametresi;
  MutexHandle: THandle;
  Tp: PThreadParametresi;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject); //Başlat
begin
  Yenithread := Threadim.Create; // 1. Thread oluşturuluyor
  Yenithread.Priority := tpLowest;
  Yenithread.OnTerminate := threadbitti;
  Yenithread.Resume;  // 1. Thread başlatılıyor

  Yenithread1 := Threadim.Create; // 2. Thread oluşturuluyor
  Yenithread1.Priority := tpLowest;
  Yenithread1.OnTerminate := threadbitti2;
  Yenithread1.Resume; // 2. Thread başlatılıyor

  BitBtn1.Enabled := False;
  // Button pasif yapılıyor
  // Çünkü  her tıklamada farklı
  // 2 thread daha oluşacaktır
end;

procedure TForm1.threadbitti(sender: tobject);
begin
 Showmessage('1. Thread bitti !');
end;

procedure TForm1.threadbitti2(sender: tobject);
begin
 Showmessage('2. Thread bitti !');
end;

procedure TForm1.MesajAlicisi(var Msg: TMessage);
begin
 Tp := PThreadParametresi(Msg.WParam);
 Form1.Caption := tp.yazi + ' - Çalışan Thread Id : ' + Inttostr(tp.Id);
 ProgressBar1.position := tp.sayi;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 New(ThreadParametresi);
 // Mesaj için bilgi saklanacak
 // yapı hafızda oluşturuluyor
 MutexHandle := CreateMutex(nil, false, nil); 
 // Yeni mutex oluşturuluyor
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Dispose(ThreadParametresi);
 // Mesaj için bilgi saklanan
 // yapı hafızdan siliniyor
 CloseHandle(MutexHandle);
 // Mutex siliniyor
end;

end.

Değiştirilmiş Thread Unitin son hali (Unit2.pas) :

unit Unit2;

interface

uses Classes, SysUtils, Windows;

type
  Threadim = class(TThread)
  private  { Private declarations }
   say:integer;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses Unit1;

constructor Threadim.Create;
begin
  inherited Create(True);
  // True = Thread otomatik başlamasın, Resume Metodunu Beklesin
  // False = Thread otomatik başlasın
  FreeOnTerminate := True;
  // Thread Terminate Edildiğinde Class Free Olsun
end;

procedure Threadim.Execute;
begin
  WaitForSingleObject(MutexHandle, INFINITE);
  // Mutex Bekleniyor
  say := 0;
  while ((say<100) and not(Terminated)) do begin
   Inc(say);
   ThreadParametresi^.yazi := IntToStr(say);
   ThreadParametresi^.sayi := say;
   ThreadParametresi^.Id := ThreadID;
   PostMessage(Form1.Handle, WM_PBAR_DEGERI_MESAJIM, Integer(ThreadParametresi), 0);
   Sleep(150);
  end;
  ReleaseMutex(MutexHandle);
  // Mutex serbest bırakılıyor
end;

destructor Threadim.Destroy;
begin
 inherited;
end;

end.

Uygulamada bulunan Butona tıklandığında aynı sınıftan 2 adet Thread oluşturulur ve çalıştırılır. Thread sınıfına Mutex kontrol mekanizması konulduğu için ilk çalışan thread normal çalışırken ikinci thread birincinin işini bitirip Mutex’i serbest bırakmasını bekler. Uygulama başlığında progressbar’ın durumunun yanında aynı zamanda o anki çalışan thread’in ThreadId’si yazmaktadır. Thread’ler sayı değişkenin yanında ThreadID’sini de parametre olarak göndermektedir. Birinci thread bittiğinde “1. Thread bitti !”, ikinci thread bittiğinde “2. Thread bitti !” mesajı çıkmaktadır.

Uygulamaların açıklamaları kodların yanında kısa şekillerde verilmiştir. Uygulamalarda sonradan değişiklik yapılabilir.

Reklamlar

Thread (Kanal) nedir? Delphi’de Thread kullanımı örnekleri” üzerine 8 düşünce

  1. HyperXMan

    Hocam selamlar, makalenin okudum çok açık ve bilgilendirici olmuş teşekkürler.

    Konu hakkında bir sorum olacaktı. Bundan 2 yıla yakın zaman önce Delphi forumlarindan birinde bir dosya indirmistim. Bazı SQL sorgularinda programda kasmalar oluyordu. Bunun için o forumda bulduğum bir class dosyası ile bazı fonksiyonları kullanarak programın kasmasini önlüyordum. Olayı şu idi;

    İşlevde olan prosedürü arka plana alıp programı kastirmadan islemi yapmaktı. Sorum şu; bu yapılan işlem thread miydi? Konu hakkında thread olup olmadığını yazılmamıştı.

    Teşekkürler,
    İyi çalışmalar.

    Cevapla
    1. dm7admin Yazıyı Yazan

      Merhaba
      Yazıyı beğendiğiniz için Teşekkür ederim. Umarım faydalı olmuştur.

      Bir sınıfın thread olması için TThread sınıfından türetilmiş olmalıdır veya CreateThread api’si veya BeginThread komutu ile oluşturulmalıdır. Büyük ihtimal thread’dir. Fakat başka yöntemler de var. Yani kodu görmeden kesin bilgi veremem.

      Saygılarımla.

      Cevapla
      1. HyperXMan

        İlginiz için teşekkürler. Bu soruyu sormamda ki amaç bilgimin doğruluğunu kanıtlamaktır, çünkü ilerde birisi sorduğunda yanlış ifade kullanmış olmayayım 🙂

        Class dosyasını inceledim fakat dediğiniz gibi TThread sınıfını göremedim, package türünde yazılmış bir class dosyası gibi 🙂 Dosyanın linkini alt tarafa bırakıyorum hocam, incelerseniz sevinirim.

        http://www.delphiturkiye.com/forum/viewtopic.php?t=33923

        NOT: Hocam makaleyi tekrar bulup okuma imkanım şimdi oldu. Makalede Thread olduğu söyleniyor.

        İlginiz için tekrar teşekkürler,

        Saygılarımla,
        Halil Han Badem.

  2. Murathan Gökdemir

    Anlatım için çok teşekkürler , oldukça güzel ve başarılı bir anlatım olmuş , sizden ricam mümkünse Thread ile bir database işlemi yapabilirseniz, Bağlantı ve kayıt ekleme silme tarzında.

    Teşekkürler iyi çalışmalar.

    Cevapla

Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

w

Connecting to %s