Tôi đã đọc tài liệu về điều này và tôi nghĩ rằng tôi hiểu. Một AutoResetEvent
thiết lập lại khi mã đi qua event.WaitOne()
, nhưng một ManualResetEvent
không.
Điều này có đúng không?
Tôi đã đọc tài liệu về điều này và tôi nghĩ rằng tôi hiểu. Một AutoResetEvent
thiết lập lại khi mã đi qua event.WaitOne()
, nhưng một ManualResetEvent
không.
Điều này có đúng không?
Câu trả lời:
Đúng. Nó giống như sự khác biệt giữa phí cầu đường và cửa. Các ManualResetEvent
là cánh cửa, mà cần phải được đóng lại (reset) bằng tay. Đây AutoResetEvent
là 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.
Chỉ cần tưởng tượng rằng các AutoResetEvent
thực thi WaitOne()
và Reset()
như là một hoạt động nguyên tử duy nhất.
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ó).
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.
Tôi tạo ra ví dụ đơn giản để làm sáng tỏ sự hiểu biết của ManualResetEvent
vs 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();
}
}
}
autoResetEvent.WaitOne()
tương tự như
try
{
manualResetEvent.WaitOne();
}
finally
{
manualResetEvent.Reset();
}
như một hoạt động nguyên tử
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);
}
}
}
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);
}
}
}
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.
Đú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.
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.
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:
Đâ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ó.