Back to technical post....
Seorang rekan .NET developer yang bekerja di sebuah koran ternama Indonesia ingin membuat sebuah scheduler seperti cronjob. Contohnya pada waktu 12:00:30 (persis 30 detik setelah jam 12), kirim email ke seluruh karyawan menghimbau mereka untuk makan siang.
Solusi beliau sebelumnya adalah dengan menggunakan timer dengan interval 1 detik, dan misal pada detik itu waktu menunjukkan 12:00:30, maka kirim email sekarang juga.
Lantas dia menanyakan apakah ada solusi lain, mengingat aplikasi akan menjadi lambat seiring dengan semakin banyaknya cronjob yang harus di schedule?
[Jawaban Singkat]
Ada, dan berikut solusinya:
static void Main(string[] args)
{
System.Threading.Timer t = new System.Threading.Timer(
new System.Threading.TimerCallback(Halo),
null,
kerjakanPada(22,20,45),
new TimeSpan(-1)
);
Console.ReadLine();
}
static TimeSpan kerjakanPada(int h, int m, int s)
{
DateTime today = DateTime.Now;
DateTime alarm = new DateTime(
today.Year, today.Month, today.Day,
h, m, s);
TimeSpan span = alarm - DateTime.Now;
return span;
}
static void Halo(object o) {
Console.WriteLine("Halo!");
}
Solusi di atas akan menulis "Halo" di Console tepat pada jam 10.20 malam lewat 45 detik hari ini (jam artikel ini ditulis
)
[Jawaban Panjang Lebar]
Loh koq bisa? Ya, ini karena ada 3 timer di .Net:
- System.Windows.Forms.Timer
- System.Timers.Timer
- System.Threading.Timer
Dan Timer yang terakhir bisa dikatakan sebagai "alarm clock sejati", lihat saja constructor-nya:
public Timer(TimerCallback callback, object state, TimeSpan dueTime,
TimeSpan period);
parameter dueTime ini bisa digunakan kapan callback akan dipanggil, sehingga bisa digunakan sebagai scheduler/alarm clock/reminder!
parameter period jika kita isi dengan -1 maka akan menyebabkan callback hanya dipanggil sekali saja (pada saat dueTime).
So, apa perbedaan ketiga Timer ini dan mengapa musti ada 3?
Thread?
System.Windows.Forms.Timer: UI Thread
System.Timers.Timer: UI atau Worker Thread
System.Threading.Timer: Worker Thread
Thread-Safe?
System.Windows.Forms.Timer: Tidak
System.Timers.Timer: Ya
System.Threading.Timer: Tidak
OOP?
System.Windows.Forms.Timer: Ya
System.Timers.Timer: Ya
System.Threading.Timer: Tidak
Butuh WinForms?
System.Windows.Forms.Timer: Ya
System.Timers.Timer: Tidak. Jalan di Console app.
System.Threading.Timer: Tidak. Jalan di Console app.
Callback dapat menerima argumen (state object)?
System.Windows.Forms.Timer: Tidak
System.Timers.Timer: Tidak
System.Threading.Timer: Ya
Callback dapat di-schedule?
System.Windows.Forms.Timer: Tidak
System.Timers.Timer: Tidak
System.Threading.Timer: Ya
Jadi masing-masing punya kelebihan sendiri...
Tapi perbedaan yang utama mungkin yang pertama: apakah timer dijalankan di UI Thread atau di Worker Thread?
Sekali lagi: apa bedanya Bro?
Well, UI Thread itu khusus specific menangani UI Events (Click dan Paint yang paling sering). Semua kode yang akan mengubah tampilan UI (scrollbar, progressbar, update label, dsb.) harus dilakukan di UI Thread ini.
Kenapa di-design seperti itu ya? Karena programming-nya jadi simple. Kita nggak usah nge-lock Label1 sebelum mengubah Label1.Text. Istilah kerennya: no explicit synchronization 
OK, perhatikan text diagram berikut:
UI Thread <-- [ Click@Button1, Click@Button2, ... ]
Sebelah kanan itu adalah Message Queue. Sebagai sebuah Queue yang bersifat First-In-First-Out (FIFO), Click@Button1 akan diproses lebih dahulu oleh UI Thread.
OK, sekarang bayangkan Button1_Click event handler berisi kode yang meng-sorting 1000000 data dengan algoritma BubbleSort.
Apa yang akan terjadi?
Yup, aplikasi akan kelihatan "hang", titlebar berubah menjadi "Not Responding".
Jadi gimana solusinya? Jalankan BubbleSort di Worker Thread (ada bermacam cara, BeginInvoke atau lewat ThreadPool). Dan jika setelah selesai masih perlu meng-update Label text, panggil delegate UpdateLabelText dengan Label.BeginInvoke (yang akan menjalankan kode di UI Thread).
Coba test kode ini di aplikasi WinForm Anda (layout 2 button + 1 label):
delegate void IsengAja();
private void button1_Click(object sender, EventArgs e)
{
// Ubah UI sebelum pindah ke Worker Thread
label1.Text = DateTime.Now.Second.ToString();
button1.Enabled = false;
System.Threading.ThreadPool.QueueUserWorkItem(
delegate
{
// simulasi kerja 30 detik
System.Threading.Thread.Sleep(30000);
// Pindah lagi ke UI Thread
// baca MSDN tentang Control.BeginInvoke
label1.BeginInvoke(new IsengAja(delegate
{
label1.Text = DateTime.Now.Second.ToString() +
" Selesai Bro!";
button1.Enabled = true;
}));
});
}
Untung ada Anonymous Method di .NET 2.0, kalo ga kodenya bisa lebih panjang euy!
Dan bandingkan dengan kode ini:
private void button2_Click(object sender, EventArgs e)
{
label1.Text = DateTime.Now.Second.ToString();
button1.Enabled = false;
System.Threading.Thread.Sleep(30000);
label1.Text = DateTime.Now.Second.ToString();
button1.Enabled = true;
}
Mana yg kelihatan "lebih cepat?". Atau istilah tepatnya, mana yang kelihatan "lebih responsif?"
Kembali ke perbincangan awal tentang Timer. Ingat System.Windows.Forms.Timer berjalan di UI Thread. So kalau Anda merasa Timer di WinForms koq lambat, cek 2 hal:
1. Apakah Anda menggunakan versi Timer yang sesuai dengan kebutuhan? Lihat dokumentasi MSDN untuk System.Windows.Forms.Timer, System.Timers.Timer dan System.Threading.Timer
2. Jika Anda menggunakan System.Windows.Forms.Timer, pastikan Anda hanya melakukan kode yang mengubah UI disini. Jika ada kode non-UI, delegasikan ke Worker Thread!
[Bacaan Tambahan]
- Application Responsiveness, Joe Duffy (Microsoft CLR Guy), at Dr. Dobb's Journal http://www.ddj.com/dept/windows/192700235