I have simplified the scenario as that I have as good as possible. Down below you find the complete code with which you can test the program yourself. In my use case I have to wait for a reset event from another Thread - lets call that subEvent. The event subEvent is signaled once again another thread is finished. Lets call this resetEvent. In this simplified version I signal resetEvent after 200ms and that signals subEvent immediately. This is implemented in the SingleCascaded
method.
This example works as expected. The output for me is
Sub Thread: 229 ms
Main 229 ms
Thread: 229 ms
Well the 30ms is still a heavy price, but it becomes far worse, when I increase the number of waits of both reset events - this is closer to the scenario I have in my program. In the method MultipleCascaded
I create 5 threads that wait for the resetEvent - which is set after 200 ms - and 15 threads that wait for the subEvent - which is set after the resetEvent.
Set reset event 641
Main 641 ms
Thread 4: 660 ms
Thread 1: 661 ms
Thread 3: 662 ms
Sub Thread 4.0.: 662 ms
Sub Thread 4.1.: 663 ms
Sub Thread 0.0.: 661 ms
Sub Thread 0.1.: 661 ms
Sub Thread 1.1.: 664 ms
Sub Thread 1.2.: 664 ms
Sub Thread 3.0.: 665 ms
Sub Thread 3.1.: 665 ms
Sub Thread 3.2.: 666 ms
Thread 0: 661 ms
Sub Thread 4.2.: 663 ms
The interesting part here is, that the Set\Wait
method of the reset event does not produce the time gap. It seems that in this case the timer cannot execute after the designated time.
Of course in my application these are no timers, things like I/O access, computation and the like. But the effect is the same. There is a visible time gap in the execution where seemingly nothing happens. I had the issue in other scenarios as well (overly use of async/await for example). But there I could not reproduce it in a small example.
My question now is. What is happening? And far more important, how can I work around that? Here is what I have found out what is not the problem:
- All threads in the thread pool are blocked - There are a lot of threads that can be active at the same time
- The timer - as I mentioned in my actual application I do I/O accesses and computational work. There are no timers.
- The implementation of this particular reset event - This happens also when I use
ManualResetEvent
andAutoResetEvent
and in my old application I worked with async/await; in this case it is aTaskCompletionSource
Here now the code for you to test it.
class Program
{
static void Main(string[] args)
{
SingleCascaded();
MultipleCascaded();
}
private static void MultipleCascaded()
{
Console.WriteLine("Test multiple waits cascaded");
ManualResetEventSlim resetEvent = new ManualResetEventSlim(false);
Stopwatch watch = new Stopwatch();
watch.Start();
for (int j = 0; j < 5; j++)
{
ThreadPool.QueueUserWorkItem(state =>
{
ManualResetEventSlim subEvent = new ManualResetEventSlim(false);
for (int k = 0; k < 3; k++)
{
ThreadPool.QueueUserWorkItem(state =>
{
subEvent.Wait();
Console.WriteLine(
$"Sub Thread {((Tuple<int, int>) state).Item1}.{((Tuple<int, int>) state).Item2}.: {watch.ElapsedMilliseconds} ms");
}, new Tuple<int, int>((int) state, k));
}
resetEvent.Wait();
subEvent.Set();
Console.WriteLine($"Thread {state}: {watch.ElapsedMilliseconds} ms");
}, j);
}
using Timer timer = new Timer(state =>
{
Console.WriteLine($"Set reset event {watch.ElapsedMilliseconds}");
resetEvent.Set();
}, null, 200,
Timeout.Infinite);
}
private static void SingleCascaded()
{
Console.WriteLine("Test single waits cascaded");
ManualResetEventSlim resetEvent = new ManualResetEventSlim(false);
Stopwatch watch = new Stopwatch();
watch.Start();
ThreadPool.QueueUserWorkItem(state =>
{
ManualResetEventSlim subEvent = new ManualResetEventSlim(false);
ThreadPool.QueueUserWorkItem(state =>
{
subEvent.Wait();
Console.WriteLine(
$"Sub Thread: {watch.ElapsedMilliseconds} ms");
});
resetEvent.Wait();
subEvent.Set();
Console.WriteLine($"Thread: {watch.ElapsedMilliseconds} ms");
});
using Timer timer = new Timer(state => { resetEvent.Set(); }, null, 200,
Timeout.Infinite);
resetEvent.Wait();
Console.WriteLine($"Main {watch.ElapsedMilliseconds} ms");
}
}