Sự khác biệt giữa ManualResetEvent và AutoResetEvent trong .NET là gì?


Câu trả lời:


920

Đúng. Nó giống như sự khác biệt giữa phí cầu đường và cửa. Các ManualResetEventlà cánh cửa, mà cần phải được đóng lại (reset) bằng tay. Đây AutoResetEventlà một trạm thu phí, cho phép một chiếc xe đi qua và tự động đóng cửa trước khi chiếc xe tiếp theo có thể đi qua.


166
Đó là một phép loại suy tuyệt vời.
twk

Thậm chí tệ hơn, đừng chờ đợi lâu để đặt IS thành WaitOne, hoặc nó sẽ được đặt lại trong thời gian này. Đã có nhiều chủ đề bị bỏ rơi với điều đó.
Oliver Friedrich

24
Hoặc giống như một cánh cửa và một cửa quay.
Constantin

9
Ồ, đó là lý do tại sao họ được đặt tên là những gì họ đang có.
Arlen Beiler

1
@DanGoldstein tốt, vì đây là không đóng cửa và trong trường hợp ai đó muốn nó: msdn.microsoft.com/en-us/library/...
Phil N DeBlanc

124

Chỉ cần tưởng tượng rằng các AutoResetEventthực thi WaitOne()Reset()như là một hoạt động nguyên tử duy nhất.


16
Ngoại trừ việc nếu bạn đã thực thi WaitOne và Đặt lại dưới dạng một thao tác nguyên tử duy nhất trong sự kiện ManualResetEvent, nó vẫn sẽ làm một cái gì đó khác với AutoResetEvent. ManualResetEvent phát hành tất cả các luồng chờ cùng một lúc, trong đó AutoResetEvent đảm bảo chỉ phát hành một luồng chờ.
Martin Brown

55

Câu trả lời ngắn gọn là có. Sự khác biệt quan trọng nhất là AutoResetEvent sẽ chỉ cho phép một chuỗi chờ duy nhất tiếp tục. Mặt khác, một ManualResetEvent sẽ tiếp tục cho phép các luồng, một số cùng một lúc, tiếp tục cho đến khi bạn bảo nó dừng lại (Đặt lại nó).


36

Lấy từ cuốn sách Nutshell C # 3.0, của Joseph Albahari

Threading trong C # - Sách điện tử miễn phí

ManualResetEvent là một biến thể của AutoResetEvent. Nó khác ở chỗ nó không tự động thiết lập lại sau khi một luồng được thực hiện trong cuộc gọi WaitOne, và do đó có chức năng như một cổng: gọi Set mở cổng, cho phép bất kỳ số luồng nào mà WaitOne ở cổng thông qua; gọi Reset đóng cổng, có khả năng, một hàng đợi người phục vụ tích lũy cho đến khi mở tiếp theo.

Người ta có thể mô phỏng chức năng này với trường "gateOpen" boolean (được khai báo bằng từ khóa dễ bay hơi) kết hợp với "spin-ngủ" - liên tục kiểm tra cờ và sau đó ngủ trong một khoảng thời gian ngắn.

ManualResetEvents đôi khi được sử dụng để báo hiệu rằng một thao tác cụ thể đã hoàn thành hoặc khởi tạo hoàn thành của một luồng và sẵn sàng thực hiện công việc.


19

Tôi tạo ra ví dụ đơn giản để làm sáng tỏ sự hiểu biết của ManualResetEventvs AutoResetEvent.

AutoResetEvent: giả sử bạn có 3 luồng công nhân. Nếu bất kỳ trong số các luồng đó sẽ gọi WaitOne()tất cả 2 luồng khác sẽ dừng thực thi và chờ tín hiệu. Tôi giả sử họ đang sử dụng WaitOne(). Nó là như thế; nếu tôi không làm việc, không ai làm việc Trong ví dụ đầu tiên bạn có thể thấy rằng

autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();

Khi bạn gọi Set()tất cả các chủ đề sẽ làm việc và chờ tín hiệu. Sau 1 giây tôi đang gửi tín hiệu thứ hai và họ thực thi và chờ ( WaitOne()). Hãy nghĩ về những kẻ này là những cầu thủ đội bóng đá và nếu một cầu thủ nói rằng tôi sẽ đợi cho đến khi người quản lý gọi cho tôi, và những người khác sẽ đợi cho đến khi người quản lý bảo họ tiếp tục ( Set())

public class AutoResetEventSample
{
    private AutoResetEvent autoReset = new AutoResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        autoReset.Set();
        Thread.Sleep(1000);
        autoReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
}

Trong ví dụ này, bạn có thể thấy rõ rằng khi bạn nhấn lần đầu tiên, Set()nó sẽ cho tất cả các luồng đi, sau 1 giây, nó báo hiệu tất cả các luồng chờ! Ngay khi bạn đặt lại chúng bất kể chúng đang gọi WaitOne()bên trong, chúng sẽ tiếp tục chạy vì bạn phải gọi thủ công Reset()để dừng tất cả chúng.

manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();

Đó là thông tin thêm về mối quan hệ Trọng tài / Người chơi ở đó bất kể người chơi nào bị thương và chờ chơi, những người khác sẽ tiếp tục làm việc. Nếu Trọng tài nói chờ ( Reset()) thì tất cả người chơi sẽ đợi cho đến tín hiệu tiếp theo.

public class ManualResetEventSample
{
    private ManualResetEvent manualReset = new ManualResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        manualReset.Set();
        Thread.Sleep(1000);
        manualReset.Reset();
        Console.WriteLine("Press to release all threads.");
        Console.ReadLine();
        manualReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
}

13

autoResetEvent.WaitOne()

tương tự như

try
{
   manualResetEvent.WaitOne();
}
finally
{
   manualResetEvent.Reset();
}

như một hoạt động nguyên tử


Điều này chỉ đúng về mặt khái niệm, nhưng không thực tế. Giữa WaitOne và Reset một chuyển ngữ cảnh có thể xảy ra; điều này có thể dẫn đến các lỗi tinh vi.
hofingerandi

2
Bạn có thể bỏ phiếu cho nó bây giờ? Thực tế không ai sẽ thực hiện khối mã thứ hai ở đây, đó là vấn đề hiểu được sự khác biệt.
vezenkov

11

OK, thông thường, không nên thêm 2 câu trả lời trong cùng một chủ đề, nhưng tôi không muốn chỉnh sửa / xóa câu trả lời trước đó, vì nó có thể giúp theo cách khác.

Bây giờ, tôi đã tạo, đoạn mã ứng dụng bảng điều khiển chạy để tìm hiểu toàn diện hơn và dễ hiểu hơn dưới đây.

Chỉ cần chạy các ví dụ trên hai bảng điều khiển khác nhau và quan sát hành vi. Bạn sẽ có được ý tưởng rõ ràng hơn nhiều những gì đang xảy ra đằng sau hậu trường.

Hướng dẫn đặt lại sự kiện

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class ManualResetEventSample
    {
        private readonly ManualResetEvent _manualReset = new ManualResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call ManualResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("It ran one more time. Why? Even Reset Sets the state of the event to nonsignaled (false), causing threads to block, this will initial the state, and threads will run again until they WaitOne().");
            Thread.Sleep(10000);
            Console.WriteLine();
            Console.WriteLine("This will go so on. Everytime you call Set(), ManualResetEvent will let ALL threads to run. So if you want synchronization between them, consider using AutoReset event, or simply user TPL (Task Parallel Library).");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker1 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker2 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker3 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

Hướng dẫn đặt lại đầu ra sự kiện

Tự động đặt lại sự kiện

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class AutoResetEventSample
    {
        private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call AutoResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("Nothing happened. Why? Becasuse Reset Sets the state of the event to nonsignaled, causing threads to block. Since they are already blocked, it will not affect anything.");
            Thread.Sleep(10000);
            Console.WriteLine("This will go so on. Everytime you call Set(), AutoResetEvent will let another thread to run. It will make it automatically, so you do not need to worry about thread running order, unless you want it manually!");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker1 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker2 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker3 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

Tự động đặt lại đầu ra sự kiện


đây là cách tốt nhất để hiểu tất cả, sao chép mã và chạy tất cả trong khi thay đổi một số thứ, hiểu ngay bây giờ
JohnChris

8

AutoResetEvent duy trì một biến boolean trong bộ nhớ. Nếu biến boolean là false thì nó sẽ chặn luồng và nếu biến boolean là true thì nó sẽ bỏ chặn luồng.

Khi chúng ta khởi tạo một đối tượng AutoResetEvent, chúng ta sẽ chuyển giá trị mặc định của giá trị boolean trong hàm tạo. Dưới đây là cú pháp khởi tạo một đối tượng AutoResetEvent.

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

Phương pháp WaitOne

Phương thức này chặn luồng hiện tại và chờ tín hiệu bởi luồng khác. Phương thức WaitOne đặt luồng hiện tại vào trạng thái luồng Ngủ. Phương thức WaitOne trả về true nếu nhận được tín hiệu khác trả về false.

autoResetEvent.WaitOne();

Quá tải thứ hai của phương thức WaitOne chờ số giây đã chỉ định. Nếu nó không nhận được bất kỳ chủ đề tín hiệu tiếp tục công việc của nó.

static void ThreadMethod()
{
    while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))
    {
        Console.WriteLine("Continue");
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }

    Console.WriteLine("Thread got signal");
}

Chúng tôi đã gọi phương thức WaitOne bằng cách chuyển 2 giây làm đối số. Trong vòng lặp while, nó chờ tín hiệu trong 2 giây rồi nó tiếp tục công việc. Khi luồng nhận được tín hiệu WaitOne trả về true và thoát khỏi vòng lặp và in "Tín hiệu nhận được luồng".

Đặt phương thức

Phương thức AutoResetEvent Set đã gửi tín hiệu đến luồng chờ để tiến hành công việc. Dưới đây là cú pháp gọi phương thức Set.

autoResetEvent.Set();

ManualResetEvent duy trì một biến boolean trong bộ nhớ. Khi biến boolean là false thì nó chặn tất cả các luồng và khi biến boolean là đúng, nó sẽ bỏ chặn tất cả các luồng.

Khi chúng tôi khởi tạo một ManualResetEvent, chúng tôi sẽ khởi tạo nó với giá trị boolean mặc định.

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

Trong đoạn mã trên, chúng tôi khởi tạo ManualResetEvent với giá trị sai, có nghĩa là tất cả các luồng gọi phương thức WaitOne sẽ chặn cho đến khi một số luồng gọi phương thức Set ().

Nếu chúng tôi khởi tạo ManualResetEvent với giá trị thực, tất cả các luồng gọi phương thức WaitOne sẽ không chặn và tự do tiếp tục.

Phương pháp WaitOne

Phương thức này chặn luồng hiện tại và chờ tín hiệu bởi luồng khác. Nó trả về true nếu nhận được tín hiệu khác trả về false.

Dưới đây là cú pháp gọi phương thức WaitOne.

manualResetEvent.WaitOne();

Trong lần quá tải thứ hai của phương thức WaitOne, chúng ta có thể chỉ định khoảng thời gian cho đến khi luồng hiện tại chờ tín hiệu. Nếu trong thời gian nội bộ, nó không nhận được tín hiệu, nó trả về false và đi vào dòng phương thức tiếp theo.

Dưới đây là cú pháp gọi phương thức WaitOne với khoảng thời gian.

bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));

Chúng tôi đã chỉ định 5 giây vào phương thức WaitOne. Nếu đối tượng ManualResetEvent không nhận được tín hiệu trong khoảng 5 giây, nó sẽ đặt biến isSignalled thành false.

Đặt phương thức

Phương pháp này được sử dụng để gửi tín hiệu đến tất cả các luồng chờ. Phương thức Set () đặt biến boolean của đối tượng ManualResetEvent thành true. Tất cả các chủ đề chờ được bỏ chặn và tiến hành thêm.

Dưới đây là cú pháp gọi phương thức Set ().

manualResetEvent.Set();

Đặt lại phương thức

Khi chúng ta gọi phương thức Set () trên đối tượng ManualResetEvent, boolean của nó vẫn đúng. Để đặt lại giá trị, chúng ta có thể sử dụng phương thức Reset (). Đặt lại phương thức thay đổi giá trị boolean thành false.

Dưới đây là cú pháp gọi phương thức Reset.

manualResetEvent.Reset();

Chúng ta phải gọi ngay phương thức Reset sau khi gọi phương thức Set nếu chúng ta muốn gửi tín hiệu đến các luồng nhiều lần.


7

Đúng. Điều này là hoàn toàn chính xác.

Bạn có thể xem ManualResetEvent như một cách để biểu thị trạng thái. Một cái gì đó được bật (Đặt) hoặc tắt (Đặt lại). Một sự xuất hiện với một số thời gian. Bất kỳ chủ đề chờ trạng thái đó xảy ra có thể tiến hành.

AutoResetEvent có thể so sánh với tín hiệu. Một phát bắn chỉ ra rằng một cái gì đó đã xảy ra. Một sự xuất hiện mà không có bất kỳ thời gian. Thông thường nhưng không nhất thiết là "một cái gì đó" đã xảy ra là nhỏ và cần được xử lý bởi một luồng duy nhất - do đó, thiết lập lại tự động sau khi một luồng duy nhất đã tiêu thụ sự kiện.


7

Vâng đúng vậy.

Bạn có thể có được một ý tưởng bằng cách sử dụng hai.

Nếu bạn cần phải nói rằng bạn đã hoàn thành một số công việc và (các luồng) khác đang chờ điều này có thể tiến hành, bạn nên sử dụng ManualResetEvent.

Nếu bạn cần có quyền truy cập độc quyền lẫn nhau vào bất kỳ tài nguyên nào, bạn nên sử dụng AutoResetEvent.


1

Nếu bạn muốn hiểu AutoResetEvent và ManualResetEvent, bạn cần hiểu không phải phân luồng mà chỉ ngắt!

.NET muốn gợi lên lập trình cấp thấp ở xa nhất có thể.

Một ngắt là một cái gì đó được sử dụng trong lập trình cấp thấp tương đương với tín hiệu từ mức thấp trở thành cao (hoặc ngược lại). Khi điều này xảy ra, chương trình làm gián đoạn quá trình thực thi thông thường của nó và di chuyển con trỏ thực thi đến hàm xử lý sự kiện này .

Điều đầu tiên cần làm khi xảy ra gián đoạn là thiết lập lại trạng thái của nó, vì phần cứng hoạt động theo cách này:

  1. một chân được kết nối với tín hiệu và phần cứng lắng nghe nó thay đổi (tín hiệu có thể chỉ có hai trạng thái).
  2. nếu tín hiệu thay đổi có nghĩa là đã xảy ra sự cố và phần cứng đặt biến bộ nhớ cho trạng thái xảy ra (và nó vẫn như thế này ngay cả khi tín hiệu thay đổi lại).
  3. chương trình thông báo rằng các trạng thái thay đổi biến và di chuyển thực thi sang chức năng xử lý.
  4. Ở đây, điều đầu tiên cần làm, để có thể nghe lại ngắt này, là đặt lại biến bộ nhớ này về trạng thái không xảy ra.

Đây là sự khác biệt giữa ManualResetEvent và AutoResetEvent.
Nếu một ManualResetEvent xảy ra và tôi không thiết lập lại nó, lần tiếp theo nó xảy ra tôi sẽ không thể nghe nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.