ManualResetEventSlim: Gọi .set() ngay sau đó .Reset() không phát hành bất kỳ đề chờ đợiManualResetEventSlim: Calling .set() ngay sau đó .Reset() không phát hành bất kỳ * đề * chờ đợi
(Lưu ý:. Điều này cũng xảy ra với ManualResetEvent
, không chỉ với ManualResetEventSlim
)
tôi đã thử đoạn code dưới đây trong cả hai phiên bản và debug mode. Tôi đang chạy nó như là một 32-bit xây dựng bằng cách sử dụng .Net 4 trên Windows 7 64-bit chạy trên một bộ xử lý lõi tứ. Tôi biên dịch nó từ Visual Studio 2012 (vì vậy .Net 4.5 được cài đặt).
Sản lượng khi tôi chạy nó trên hệ thống của tôi là:
Waiting for 20 threads to start
Thread 1 started.
Thread 2 started.
Thread 3 started.
Thread 4 started.
Thread 0 started.
Thread 7 started.
Thread 6 started.
Thread 5 started.
Thread 8 started.
Thread 9 started.
Thread 10 started.
Thread 11 started.
Thread 12 started.
Thread 13 started.
Thread 14 started.
Thread 15 started.
Thread 16 started.
Thread 17 started.
Thread 18 started.
Thread 19 started.
Threads all started. Setting signal now.
0/20 threads received the signal.
Vì vậy, thiết lập và sau đó ngay lập tức thiết lập lại sự kiện đã không phát hành một chủ đề duy nhất. Nếu bạn bỏ ghi chú Thread.Sleep(), thì tất cả chúng đều được giải phóng.
Điều này có vẻ hơi bất ngờ.
Có ai có lời giải thích không?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
public static class Program
{
private static void Main(string[] args)
{
_startCounter = new CountdownEvent(NUM_THREADS); // Used to count #started threads.
for (int i = 0; i < NUM_THREADS; ++i)
{
int id = i;
Task.Factory.StartNew(() => test(id));
}
Console.WriteLine("Waiting for " + NUM_THREADS + " threads to start");
_startCounter.Wait(); // Wait for all the threads to have called _startCounter.Signal()
Thread.Sleep(100); // Just a little extra delay. Not really needed.
Console.WriteLine("Threads all started. Setting signal now.");
_signal.Set();
// Thread.Sleep(50); // With no sleep at all, NO threads receive the signal.
_signal.Reset();
Thread.Sleep(1000);
Console.WriteLine("\n{0}/{1} threads received the signal.\n\n", _signalledCount, NUM_THREADS);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
private static void test(int id)
{
Console.WriteLine("Thread " + id + " started.");
_startCounter.Signal();
_signal.Wait();
Interlocked.Increment(ref _signalledCount);
Console.WriteLine("Task " + id + " received the signal.");
}
private const int NUM_THREADS = 20;
private static readonly ManualResetEventSlim _signal = new ManualResetEventSlim();
private static CountdownEvent _startCounter;
private static int _signalledCount;
}
}
Lưu ý: Câu hỏi này có vẻ không có câu trả lời tương tự (ngoài việc xác nhận rằng có, điều này có thể xảy ra).
Issue with ManualResetEvent not releasing all waiting threads consistently
[EDIT]
Như Ian Griffiths chỉ ra dưới đây, câu trả lời là cơ bản Windows API được sử dụng không được thiết kế để hỗ trợ này.
Nó không may rằng the Microsoft documentation for ManualResetEventSlim.Set() bang sai rằng nó
Thiết lập trạng thái của sự kiện để báo hiệu, cho phép một hoặc nhiều đề chờ đợi vào sự kiện để tiến hành.
Rõ ràng "một hoặc nhiều" phải là "không hoặc nhiều hơn".
OK, tôi đã đi. Không ngủ, một sợi nhận được tín hiệu. Với giấc ngủ (0), 8 chủ đề, với giấc ngủ (10), tất cả 20. Vì vậy, các triệu chứng xác nhận. –
Bạn chỉ đang sử dụng đối tượng đồng bộ hóa sai, việc phát hiện xử lý chờ đợi được báo hiệu yêu cầu trình lập lịch trình chuỗi hệ điều hành để chạy. Nó là tồi tệ hơn với phiên bản Slim, nó là mỏng vì nó không thực hiện một cuộc gọi hệ điều hành. Bạn cần Monitor.Pulse() ở đây, lớp Monitor theo dõi các chủ đề chờ đợi. Một tính năng hoàn toàn bị thiếu từ MRE vì nó không thể đảm bảo sự công bằng. Nhưng bạn đã biết rằng từ bản sao được liên kết. –
Chắc chắn, trình lập lịch biểu chạy - Set() là một cuộc gọi hệ thống. –