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

queue-people-standing-in-lineKhi 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

9 thoughts on “C# – Thread Signaling: Auto và Manual Reset Event

  1. Đọc bài viết của bạn về Thread rất hay . Nhưng mình còn chỗ thắc mác này mong bạn giúp :

    //sự kiện khi click button .
    public static ArrayList ArrayList = new ArrayList();
    protected void Button1_Click(object sender, EventArgs e)
    {

    EventWaitHandle signal = new EventWaitHandle(false, EventResetMode.AutoReset);

    Thread thread = new Thread(parameter =>
    {
    Download();
    //signaling that the thread finished.
    signal.Set();
    });
    thread.Start();
    //thêm vào list
    ArrayList.Add(signal);
    }
    private void Download()
    {
    //code dơwnload
    }

    protected void Stop_Click(object sender, EventArgs e)
    {
    //Khi mình gọi ra để pause luồng trên thì không được .
    if (ArrayList.Count > 0){

    ((EventWaitHandle)ArrayList[0]).WaitOne();
    }
    }

    Trả lời
  2. Đoạn code trên nghĩa là mình muốn quản lý Thread trong 1 list . Khi nhấn vào stop thì sẽ pause Thread tương ứng lại. Khi nào Start thì lai tiếp tục. Nhưng không biết mình có làm sai hoặc hiểu sai vấn đề không , mà stop không được . Mong bạn giúp mình nhé !

    Trả lời
    • Khi sử dụng EventWaitHandle.WaitOne(), bạn phải sử dụng nó bên trong phần code mà thread thực thi. Cụ thể là bên trong phương thức Download(). Bạn có thể dùng 1 cờ gì đó để xác định khi nhấn nút Stop thì Download() sẽ gọi WaitOne().

      Trả lời
  3. Mình có 1 chương trình bằng c# là chương trình gọi cơ điện cho các tổ sản xuất, nếu tổ nào cần cơ điện thì quẹt thẻ gọi cơ điện, mỗi lần gọi sẽ được đọc đi đọc lại 3 lần, xong 3 lần đó sẽ xóa đi, mình đã dùng 3 timer nhưng chương trình chạy chậm và đôi lúc bị treo, ad cho mình hỏi mình có thẻ dùng thread được không ạ

    Trả lời

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s