Threads (Dalam Delphi)
Perkenalan
D |
i Windows 32-bit, kita dapat menjalankan banyak program secara bersamaan. Misalnya, kita dapat mencetak dokumen dan sekaligus mengecek hard disk anda aman dari virus dengan menggunakan program antivirus, dan sementara itu, untuk mengisi waktu, kita dapat main game, mengecek email, ataupun melakukan kerjaan-kerjaan lainnya.
Bagaimanapun, karena mikroposesor (CPU) hanya dapat melakukan satu operasi pada satu waktu, bagaimana mungkin menjalankan beberapa program secara bersamaan?
Well, pertama, ketika program dimuat ke dalam memori untuk dijalankan, program tersebut disebut “proses”, dan kedua, jika komputer anda hanya memiliki satu CPU, maka hanya satu proses yang sebenarnya dapat berjalan pada waktu tertentu.
Kemudian, gimana dong koq kelihatannya seperti banyak proses berjalan bersamaan? Ini jawaban yang benar: Beberapa program “terlihat” berjalan bersamaan berkat kemampuan multi-tasking yang ada pada sistem operasi.
Bagaimana cara kerja multitasking? Well, andaikan kita punya lima proses (A, B, C, D dan E). Sistem Operasi (OS) memberikan waktu yang sangat singkat pada sebuah proses (A misalnya) dan ketika waktu itu habis, proses tersebut untuk sementara dihentikan dan OS memberikan sejumlah waktu ke proses lainnya (B, misalnya), dan begitu seterusnya untuk proses lainnya (C, D, dan E dalam yang saya contohkan), sampai waktunya memberikan waktu ke proses pertama lagi (A, dalam contoh ini), dan keseluruhan siklus ini diulang lagi dan lagi. Jadi, pada akhirnya, proses berjalan sebentar, berhenti agar proses lain dapat berjalan, kemudian berjalan lagi, berhenti, dan begitu seterusnya sampai proses tersebut berakhir. Hal ini akan tampak seolah-olah program berjalan terus menerus, sama halnya seperti film yang tidak lain adalah rangkaian dari frame-frame yang bergulir dengan cepatnya, sehingga kita menangkap gerakan film tersebut.
Kemampuan multitasking Windows tidak terbatas hanya pada proses. Dalam satu proses juga dimungkinkan adanya bagian-bagian yang berjalan “bersamaan”, misalnya eksekusi beberapa thread. Biasanya, kita hanya menggunakan hanya satu thread (thread utama VCL), tetapi kita juga dapat membuat dan menjalankan thread lainnya di dalam aplikasi kita. Inilah situasi di mana aplikasi tersebut dikatakan multi-threaded.
Kapan kita memerlukan membuat thread lain? Ya, ketika kita ingin aplikasi tersebut mengerjakan lebih dari satu hal bersamaan tentunya, seperti ketika ingin melakukan task yang memerlukan banyak waktu, seperti pencarian file, tetapi kita juga ingin agar aplikasi kita bisa merespon terhadap berbagai event agar pengguna dapat menghentikan task tersebut sebelum selesai dan/atau melakukan hal lainnya dengan aplikasi tersebut.
Class TThread
c |
lass TThread (dideklarasikan dalam unit Classes) mengenkapsulasi Windows API calls yang diperlukan untuk membuat dan mengeksekusi thread yang berbeda. Class ini memiliki method abstract (dinamai Execute). Karena class tersebut bersifat abstract, kita tidak dapat membuat instance class tersebut (misalnya membuat objek dari class ini). Yang harus kita lakukan adalah membuat turunan TThread (misalnya TThread1) dan menimpa ulang method Execute. Execute ini adalah metode yang akan dijalankan pada thread yang berbeda.
Untuk mengeksekusi thread, kita harus mendeklarasikan sebuah objek dari class ini (misalnya Thread1) kemudian membangun thread tersebut:
Thread1 := TThread1.Create(False);
Parameter untuk constructor adalah properti Suspended. Jika bernilai False, Execute akan dipanggil langsung setelah objek dibuat. Pemanggilan ini akan dilakukan dalam mode asynchronous, di mana, metode Execute akan berjalan dalam thread eksekusi yang baru dan thread utama VCL juga akan tetap berjalan, dalam artian aplikasi tidak akan berhenti menunggu sampai Execute berakhir, tetapi akan tetap berjalan “bersamaan” dengan Execute.
Biasanya, thread dibuat dalam keadaan suspended (melewatkan True pada pemanggilan konstruktor), sehingga kita dapat menyesuaikan properti-properti lainnya, seperti Priority dan FreeOnTerminate, kemudian mengeset Suspended menjadi False untuk memulai eksekusi thread tersebut.
Thread dengan prioritas yang lebih tinggi akan diberikan waktu yang lebih banyak oleh CPU, dan walaupun hal tersebut dapat meningkatkan kinerja, juga dapat memperlambat thread lain dalam aplikasi, sehingga kita harus hati-hati ketika menyesuaikan nilai properti Priority.
properti FreeOnTerminate menentukan apakah objek thread dihancurkan ketika prosedur Execute berakhir, dan jika begitu, metode Destroy akan dipanggil (kita dapat menggunakannya, misalnya untuk memberitahukan ke thread utama bahwa thread tersebut berakhir).
Untuk membatalkan eksekusi sebuah thread, kita harus mengeset properti Terminated menjadi True atau memanggil metode Terminate. Ini tidak akan langsung mengakhiri thread. Untuk melakukannya, pada metode Execute, kita harus terus menerus mengecek nilai properti Terminated untuk menentukan jika ada permintaan untuk mengakhiri, dan jika demikian, clean up dan akhrii prosedur.
Contoh TThread
D |
alam contoh ini, kita akan menggunakan thread sebagai properti dari sebuah form. Form tersebut hanya akan memiliki tombol mulai (untuk memulai thread), tombol batalkan (untuk membatalkan eksekusi thread) dan sebuah kotak progres untuk menampilkan progress eksekusi thread yang akan berisi pengulangan.
Untuk mencoba contoh ini, pertama-tama buat sebuah Form dan tambahkan progress bar dan dua tombol. Pada Object Inspector, tentukan properti objek-objek berikut:
ProgressBar1
Max := 100000
Step := 1
Smooth := True
Button1
Caption := ‘Mulai’
Button2
Cancel := True
Caption := ‘Batalkan’
Enabled := False
Tombol Batalkan dinonaktifkan karena thread tidak berjalan ketika form dibuat. Ketika kita menjalankannya (pada event Click dari Button1) kita akan mengaktifkan tombol ini (sehingga pengguna dapat menghentikan thread) dan menonaktifkan tombol mulai (karena thread akan segera berjalan). Ketika thread berakhir, kita harus mengaktifkan kembali tombol mulai dan menonaktifkan tombol batalkan. Tapi, bagaimana kita mengetahui kapan thread berakhir? Salah satu cara untuk mengetahuinya adalah dengan mengeset FreeOnTerminate menjadi True sebelum menjalankan thread, sehingga ketika berakhir, objek thread tersebut akan secara otomatis dibebaskan dan pada metode Destroy-nya, kita dapat mengirimkan pesan ke form pemilik thread.
Sekarang, kita lanjutkan ke kodenya. Sebelum deklarasi type, kita akan mendeklarasikan sebuah konstanta untuk mengidentifikasi pesan yang akan dikirim dari thread ke form:
const
WM_ThreadDoneMsg = WM_User + 8;
WM_User adalah konstanta predefined yang menunjukkan nilai minimum untuk pesan-pesan yang kita buat sendiri. Nilai pesan di bawah WM_User dicadangkan untuk Windows. Delapan (8) adalah nilai bebas.
Di dalam deklarasi type, sebelum deklarasi form, kita akan mendeklarasikan thread, sebagai berikut:
TThread1 = class(TThread)
private
OwnerHandle: HWND; // Window handle of the owner form
ProgressBar1: TProgressBar; // Reference to the progress bar
procedure UpdateProgressBar; // Update ProgressBar (synchronized)
protected
procedure Execute; override; // Threaded code
published
constructor Create(Owner: TForm; ProgressBar: TProgressBar);
destructor Destroy; override;
end;
Ok, saya jelaskan:
1. Kita perlu handle dari form pemilik thread untuk bisa mengirimkan pesan WM_ThreadDoneMsg ke form tersebut dan itulah sebabnya kenapa kita mendeklarasikan properti OwnerHandle (berjenis HWND: sebuah handle jendela) dan kita menimpa metode Destroy (karena kita harus mengirimkan pesan tersebut sebelum menghancurkan objek tersebut).
2. Karena kita akan mengakses kotak progres dari form pemilik thread ini dari dalam thread, kita menggunakan acuan ke komponen tersebut. Kemungkinan lainnya bisa dengan secara langsung mengacu ke form berjenis TForm1 (kita harus mendeklarasikan class tersebut dengan klausa forward), atau berjenis TForm (kita harus melakukan typecast form sebagai TForm1 di mana kita ingin mengakses kotak progresnya), sehingga kita anggap solusi yang kita pakai adalah yang paling sederhana.
3. metode UpdateProgressBar akan dipanggil dari metode Execute untuk memperbaharui kotak progres (hanya mengubah properti step nya). Kenapa tidak mengubah langsung dari metode Execute? Untuk menghindari konflik multithreading (dua thread mengakses memori yang sama dapat memunculkan bugs pada aplikasi kita). metode UpdateProgressBar akan dipanggil menggunakan metode Synchronize, sehingga dijalankan di thread utama VCL, sehingga tidak akan konflik satu sama lain.
4. Kita menimpa metode Execute. Ini harus dilakukan jika kita ingin membuat instan class TThread1.
5. Kita mendeklarasikan konstruktor, dengan dua parameter, acuan ke form pemilik thread dan kotak progres.
Setelah selesai mendeklarasikan class TThread1, di bagian private objek TForm1, kita tambahkan deklarasi berikut:
private
Thread1: TThread1;
procedure Thread1Done(var AMessage: TMessage);
message WM_ThreadDoneMsg;
Thread1 disini adalah sebuah objek (thread itu sendiri) dari class TThread1 dan Thread1Done adalah sebuah event yang akan dieksekusi ketika form menerima pesan WM_ThreadDoneMsg. metode Message selalu menerima acuan ke objek Tmessage sebagai parameternya. Dalam contoh ini, kita tidak akan menggunakannya karena yang kita butuhkan hanyalah penerimaan pesannya saja.
Pada Object Inspector, kita buat event OnClick untuk kedua tombol dan event OnClose untuk form-nya, dan kita buat kode sebagai berikut:
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := True;
// Buat dan jalankan Thread1
Thread1 := TThread1.Create(Self, ProgressBar1);
// Eksekusi aplikasi berlanjut dan “pada waktu yang sama”
// metode Execute dari Thread1 juga berjalan.
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Thread1.Terminate; // Akhiri thread
// Eksekusi aplikasi berlanjut karena Terminate
// tidak menunggu Execute berakhir.
end;
procedure TForm1.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
if Button2.Enabled then begin // Jika thread berjalan
Thread1.Terminate; // Terminate
Thread1.WaitFor; // Tunggu sampai selesai
end;//if
Action := caFree; // Bebaskan form setelah ditutup
end;
Sekarang, kita tambahkan kode berikut dan kita selesai J
procedure TForm1.Thread1Done(var AMessage: TMessage);
// Tangkap pesan yang dikirim oleh thread yang menandakan
// selesai
begin
Button1.Enabled := True;
Button2.Enabled := False;
end;
constructor TThread1.Create(Owner: TForm;
ProgressBar: TProgressBar);
begin
inherited Create(True); // Buat thread dan hentikan
OwnerHandle := Owner.Handle; // handle jendela form pemilik
ProgressBar1 := ProgressBar; // Acuan ke kotak progress
ProgressBar1.Position := 0; // Inisialisasi kotak progress
Priority := tpNormal; // Tentukan tingkat prioritas
FreeOnTerminate := True; // Thread akan membebaskan
//sendiri ketika berakhir
Suspended := False; // Mulai eksekusi
// ==> Execute dipanggil
// Eksekusi aplikasi berlanjut dan “pada waktu yang sama”
// metode Execute dari Thread1 juga berjalan.
end;
destructor TThread1.Destroy;
// Dipanggil ketika Execute berakhir karena FreeOnTerminate
// bernilai True
begin
// Kirim pesan ke form memberitahukan bahwa thread selesai
PostMessage(OwnerHandle, WM_ThreadDoneMsg, Self.ThreadID, 0);
inherited Destroy; // Panggil metode Destroy dari turunannya
end;
procedure TThread1.Execute; // Eksekusi utama thread
var i: integer;
begin
for i := 1 to 100000 do begin
Synchronize(UpdateProgressBar); // Update kotak progress
// di thread utama VCL
if Terminated then break; // Keluar dari pengulangan
// jika thread dibatalkan
end;//for
end;
procedure TThread1.UpdateProgressBar;
// Prosedur ini dipanggil dengan Synchronize dari metode Execute
// sehingga berjalan di thread utama VCL, untuk menghindari
// konflik multithreading.
begin
ProgressBar1.StepIt; // Tingkatkan kotak progress
end;
<< Beranda