Demystifying 64 handles limit in WaitHandle.WaitAll
I have this doubt about this 64 handles limit recently for a while. We have been trying to increase the degree of parallelism in the existing application to optimize the performance and also to fully utilize the scaled out servers that we have on the grid.
In the initial stage when I started the changes, I hit the following exception System.NotSupportedException: The number of WaitHandles must be less than or equal to 64. We then introduced some threshold limit for the degree of parallelism to avoid this exception. So all are working fine afterwards.
Now after one change to another, we started to see the opportunity of increasing the parallelism degree such as below, this brought me into a doubt where this 64 handles limitation is being applied (process / app domain level) and whether this recursive method will trigger the same issue if in the end we’re waiting for more than 64 handles for all layers.
Now if we RTFM, it basically says “The WaitAll method returns when all the handles are signaled. On some implementations, if more than 64 handles are passed, a NotSupportedException is thrown”
Just to confirm this, I created a test below which will basically execute x number of high level parallel processes which will then also execute x number low level parallel processes based on the provided parameters. So the code below will try to create around 64 * 64 = 4096 handles in totals
var p = new Processor(); p.DoProcess(hiLevelParallelism : 64, loLevelParallelism : 64);
Results
05-Apr-2011 00:42:06.781 - Thead#13 - Start queueing 64 high level processes
05-Apr-2011 00:42:06.815 - Thead#13 - Waiting for all 64 high level handles to complete
05-Apr-2011 00:42:07.049 - Thead#17 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
05-Apr-2011 00:42:07.141 - Thead#15 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
05-Apr-2011 00:42:07.287 - Thead#24 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
..
.. omitted 58 similar lines here
..
05-Apr-2011 00:42:08.087 - Thead#28 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
05-Apr-2011 00:42:08.179 - Thead#27 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
05-Apr-2011 00:42:08.297 - Thead#17 - DoHighLevelProcess - Completed with 64 LowLeveProcesses
05-Apr-2011 00:42:08.455 - Thead#13 - All processes have been completed
It always feel much better if we see it running in the code, doesn’t it? :)
Source code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
using System.Diagnostics;
namespace ParallelismTests
{
[TestClass]
public class WaitHandleTest
{
[TestMethod]
public void TestMethod1()
{
var p = new Processor();
p.DoProcess(hiLevelParallelism : 64, loLevelParallelism : 64);
}
}
public class Processor
{
public void DoProcess(int hiLevelParallelism, int loLevelParallelism)
{
SetTurboMode();
Debug("Start queueing {0} high level processes", hiLevelParallelism.ToString());
var r = new Random();
var hiLevelHandles = new AutoResetEvent[hiLevelParallelism];
for (int i = 0; i < hiLevelParallelism; i++)
{
var hiLevelHandle = new AutoResetEvent(false);
hiLevelHandles[i] = hiLevelHandle;
ThreadPool.QueueUserWorkItem((s) =>
{
DoHighLevelProcess(r, loLevelParallelism);
hiLevelHandle.Set();
});
}
Debug("Waiting for all {0} high level handles to complete", hiLevelParallelism.ToString());
WaitHandle.WaitAll(hiLevelHandles);
Debug("All processes have been completed");
}
private void DoHighLevelProcess(Random r, int loLevelParallelism)
{
var loLevelHandles = new AutoResetEvent[loLevelParallelism];
for (int i = 0; i < loLevelParallelism; i++)
{
var loLevelHandle = new AutoResetEvent(false);
loLevelHandles[i] = loLevelHandle;
ThreadPool.QueueUserWorkItem((s) =>
{
DoLowLevelProcess(r.Next(1, 10));
loLevelHandle.Set();
});
}
WaitHandle.WaitAll(loLevelHandles);
Debug("DoHighLevelProcess - Completed with {0} LowLeveProcesses", loLevelParallelism);
}
private void DoLowLevelProcess(int Sleep)
{
Thread.Sleep(Sleep);
//Debug("DoLowLevelProcess - Completed after {0} ms", Sleep.ToString());
}
/// <summary>
/// oh i just wish the framework would have this in place like Console.WriteLine
/// </summary>
/// <param name="format"></param>
/// <param name="<span class=" />args">
private static void Debug(string format, params object[] args)
{
System.Diagnostics.Debug.WriteLine(
string.Format("{0} - Thead#{1} - {2}",
DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss.fff"),
Thread.CurrentThread.ManagedThreadId.ToString(),
string.Format(format, args)));
}
/// <summary>
/// This is not intended for production purpose
/// </summary>
private static void SetTurboMode()
{
int t, io;
ThreadPool.GetMaxThreads(out t, out io);
Debug("Default Max {0}, I/O: {1}", t, io);
var success = ThreadPool.SetMinThreads(t, io);
Debug("Successfully set Min {0}, I/O: {1}", t, io);
}
}
}


Trackbacks