C# – Thread Signaling: Auto và Manual Reset Event

Khi làm việc với Thread, đôi lúc bạn cần các thread thực thi theo một trình tự nào đó để đảm bảo các tác vụ diễn ra một cách hợp lý. Như vậy, các thread cần phải sử dụng một cơ chế nào đó để đợi và thông báo cho nhau. Cơ chế này được gọi là Signaling và có nhiều cách thức để thực hiện, tuy nhiên trong bài này tôi chỉ tập trung giới thiệu về hai lớp AutoResetEvent ManualResetEvent.

Giới thiệu

Hai lớp Event này được thừa kế trực tiếp từ EventWaitHandle. Một đối tượng event sẽ có hai trạng thái là:
signaled: cho phép các thread đang đợi tiếp tục chạy.
non-signaled: các thread sẽ bị chặn (block).

EventWaitHandle có ba phương thức chính bạn cần quan tâm:
Close: giải phóng các tài nguyên được sử dụng bởi WaitHandle.
Reset: chuyển trạng thái của event thành non-signaled.
Set: chuyển trạng thái của event thành signaled.
WaitOne([parameters]): Chặn thread hiện tại cho đến khi trạng thái của event được chuyển sang signaled.

Cách sử dụng AutoResetEvent và ManualResetEvent là như nhau tuy nhiên chúng có một chút khác biệt sẽ được trình bày sau. Bây giờ ta hãy đi vào một ví dụ cụ thể.

Sử dụng

Khi tạo một đối tượng AutoResetEvent hay ManualResetEvent, bạn có thể xác dịnh trạng thái cho nó bằng cách truyền một tham số boolean vào constructor, với true cho trạng thái signaled và ngược lại. Bạn cũng có thể tạo một event bằng lớp EventWaitHandle với tham số thứ hai là một kiểu enum EventResetMode.

using System;
using System.Threading;
class Program
{

    static void Main()
    {
        var signal = new EventWaitHandle(false,EventResetMode.AutoReset);
		// or signal = new AutoResetEvent(false);

        new Thread(() =>
        {
            Console.WriteLine("Waiting on EventWaitHandle");
            signal.WaitOne();

            Console.WriteLine("Got the signal from EventWaitHandle.");

        }).Start();

        Thread.Sleep(200);
        Console.WriteLine("Press Enter to release the waiting thread.");
        Console.ReadLine();

        signal.Set();

        Console.Read();
    }
}

// OUT PUT:
/*
Waiting on EventWaitHandle
Press Enter to release the waiting thread.
{ENTER}
Got the signal from EventWaitHandle.
*/

Cách hoạt động này giống như đèn giao thông. Tín hiệu chuyển sang đỏ (non-signaled) là bắt buộc các xe phải “wait”. Khi tín hiệu chuyển sang xanh (signaled) thì xe cộ mới được tiếp tục lưu thông.

gdp_road-position

(Src: rulesoftheroad.ie)

Cross-Process

Bạn có thể sử dụng chung một EventWaitHandle giữa các tiến trình (process) khác nhau trong hệ thống. Điều này giúp các tiến trình có thể tương tác và đồng bộ với nhau trong các tác vụ cần thiết. Để thực hiện, bạn chỉ cần đặt tên cho đối tượng EventWaitHandle khi tạo nó. Ví dụ tôi tạo một đối tượng EventWaitHandle với tên là “Foo.Bar:

var signal = new EventWaitHandle(false, EventResetMode.AutoReset, “Foo.Bar”);

Bạn sẽ tạo cùng một đối tượng này trong các process cần dùng chung nó và cách sử dụng giống như trong phần trên. Ví dụ, bạn tạo một Console project với lớp Program1 như sau:

using System;
using System.Threading;

class Program1
{

    static void Main()
    {
        var signal = new EventWaitHandle(false, EventResetMode.AutoReset, "Foo.Bar");

        Console.WriteLine("Press Enter to release the waiting thread.");
        Console.ReadLine();

        signal.Set();

        Console.Read();
    }
}

Thêm một project mới vào solution với lớp Program2:

using System;
using System.Threading;

class Program2
{
    static void Main(string[] args)
    {
        var signal = new EventWaitHandle(false, EventResetMode.AutoReset, "Foo.Bar");

        new Thread(() =>
        {
            Console.WriteLine("Waiting on Foo.Bar");
            signal.WaitOne();

            Console.WriteLine("Got the signal from Foo.Bar.");

        }).Start();

        Console.Read();
    }
}

Note: Để chạy được nhiều project trong một solution, bạn vào nhấn phải vào Solution > Properties > Common Properties > Startup Project và chọn Multiple startup projects.
Khi cần chạy project nào, bạn nhấn phải project đó chọn Debug > Start new instance.

Sau khi chạy hai project một lúc, bên Program1, nếu bạn nhấn {ENTER} thì Program2 sẽ hiển thị thông báo nhận được tín hiệu.

AutoResetEvent vs ManualResetEvent

Cách sử dụng hai lớp này tương tự nhau, nhưng cách hoạt động của chúng có một điểm khác biệt duy nhất. Đó là khi gọi phương thức Set():
ManualResetEvent: giải phóng tất cả thread đang đợi. Nếu muốn trở lại trạng thái non-signaled, bạn phải gọi phương thức Reset().
AutoResetEvent: chỉ giải phóng một thread đang đợi và lập tức chuyển trang trạng thái non-signaled. Do vậy, bạn không cần phải dùng đến phương thức Reset().

Bạn có thể hình dung ManualResetEvent giống như cửa siêu thị, mỗi khi mở ra là bất kì ai cũng có thể tự do đi vào. Còn AutoResetEvent giống như cửa vào bãi giữ xe, mỗi người muốn ra/vào phải nhận/trả vé, theo thứ tự từng người một.

YinYangIt’s Blog