Example #1
0
        /// <summary>
        /// Indicates to the EngineProxy that it is no longer needed.
        /// Called by TaskEngine when the task using the EngineProxy is done.
        /// </summary>
        internal void MarkAsInActive()
        {
            activeProxy = false;

            // Since the task has a pointer to this class it may store it in a static field. Null out
            // internal data so the leak of this object doesn't lead to a major memory leak.
            loggingServices   = null;
            parentModule      = null;
            buildEventContext = null;

            // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done)
            // this will be null if the engineproxy was never sent accross an appdomain boundry.
            if (sponsor != null)
            {
                ILease lease = (ILease)RemotingServices.GetLifetimeService(this);

                if (lease != null)
                {
                    lease.Unregister(sponsor);
                }

                sponsor.Close();
                sponsor = null;
            }
        }
Example #2
0
        /// <summary>
        /// This constructor is used by the class internally to create new instances when a thread
        /// becomes blocked by a user code callback.
        /// </summary>
        private TaskWorkerThread
        (
            TaskExecutionModule parentModule,
            ManualResetEvent exitTaskThreads,
            ExitTaskCache exitTaskThreadsCache,
            Queue <TaskWorkerThread> workerThreadQueue,
            Hashtable handleIdToWorkerThread,
            Queue <TaskExecutionState> workItemQueue,
            ManualResetEvent workItemInsertionEvent,
            Hashtable waitingTasks,
            bool profileExecution
        )
        {
            this.parentModule           = parentModule;
            this.exitTaskThreads        = exitTaskThreads;
            this.exitTaskThreadsCache   = exitTaskThreadsCache;
            this.workerThreadQueue      = workerThreadQueue;
            this.handleIdToWorkerThread = handleIdToWorkerThread;
            this.workItemQueue          = workItemQueue;
            this.workItemInsertionEvent = workItemInsertionEvent;
            this.waitingTasks           = waitingTasks;
            this.profileExecution       = profileExecution;

            InitializePerInstanceData();
        }
Example #3
0
        /// <summary>
        /// Executes a task within a target. This method initializes a task engine for the given task, and then executes the task
        /// using the engine.
        /// </summary>
        /// <param name="taskNode"></param>
        /// <param name="hostObject"></param>
        /// <returns>true, if successful</returns>
        internal bool ExecuteOneTask(XmlElement taskNode, ITaskHost hostObject)
        {
            bool taskExecutedSuccessfully = false;

            string            projectFileOfTaskNode   = XmlUtilities.GetXmlNodeFile(taskNode, parentProject.FullFileName);
            BuildEventContext targetBuildEventContext = new BuildEventContext
                                                        (
                ParentProject.ProjectBuildEventContext.NodeId,
                this.id,
                ParentProject.ProjectBuildEventContext.ProjectContextId,
                ParentProject.ProjectBuildEventContext.TaskId
                                                        );
            int handleId = parentEngine.EngineCallback.CreateTaskContext(ParentProject, this, null, taskNode,
                                                                         EngineCallback.inProcNode, targetBuildEventContext);
            TaskExecutionModule taskExecutionModule = parentEngine.NodeManager.TaskExecutionModule;
            TaskEngine          taskEngine          = new TaskEngine(taskNode, hostObject, parentProject.FullFileName, projectFileOfTaskNode, parentEngine.LoggingServices, handleId, taskExecutionModule, targetBuildEventContext);

            taskExecutedSuccessfully =
                taskEngine.ExecuteTask
                (
                    TaskExecutionMode.ExecuteTaskAndGatherOutputs,
                    new Lookup(parentProject.evaluatedItemsByName, parentProject.evaluatedProperties, ParentProject.ItemDefinitionLibrary)
                );

            return(taskExecutedSuccessfully);
        }
Example #4
0
        /// <summary>
        /// Creates an instance of this class for the specified task.
        /// </summary>
        public TaskEngine
        (
            XmlElement taskNodeXmlElement,
            ITaskHost hostObject,
            string projectFileOfTaskNode,
            string parentProjectFullFileName,
            EngineLoggingServices loggingServices,
            int handleId,
            TaskExecutionModule parentModule,
            BuildEventContext targetBuildEventContext
        )
        {
            ErrorUtilities.VerifyThrow(taskNodeXmlElement != null, "Need to specify the task node.");
            ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need to specify path of project.");
            ErrorUtilities.VerifyThrow(parentProjectFullFileName != null, "Need to specify name of project.");
            ErrorUtilities.VerifyThrow(loggingServices != null, "Need to specify the node logger.");

            this.taskNode = taskNodeXmlElement;
            this.taskClass = null;
            this.hostObject = hostObject;
            this.projectFileOfTaskNode = projectFileOfTaskNode;
            this.parentProjectFullFileName = parentProjectFullFileName;
            this.loggingServices = loggingServices;
            this.handleId = handleId;
            this.parentModule = parentModule;
            this.continueOnError = false;
            this.conditionAttribute = taskNode.Attributes[XMakeAttributes.condition];
            this.buildEventContext = targetBuildEventContext;
        }
Example #5
0
        /// <summary>
        /// This constructor is used by the class internally to create new instances when a thread
        /// becomes blocked by a user code callback.
        /// </summary>
        private TaskWorkerThread
        (
            TaskExecutionModule parentModule,
            ManualResetEvent exitTaskThreads,
            ExitTaskCache exitTaskThreadsCache,
            Queue<TaskWorkerThread> workerThreadQueue,
            Hashtable handleIdToWorkerThread,
            Queue<TaskExecutionState> workItemQueue,
            ManualResetEvent workItemInsertionEvent,
            Hashtable waitingTasks,
            bool profileExecution
        )
        {
            this.parentModule = parentModule;
            this.exitTaskThreads = exitTaskThreads;
            this.exitTaskThreadsCache = exitTaskThreadsCache;
            this.workerThreadQueue = workerThreadQueue;
            this.handleIdToWorkerThread = handleIdToWorkerThread;
            this.workItemQueue = workItemQueue;
            this.workItemInsertionEvent = workItemInsertionEvent;
            this.waitingTasks = waitingTasks;
            this.profileExecution = profileExecution;

            InitializePerInstanceData();
        }
Example #6
0
        /// <summary>
        /// This constructor creates a worker thread which is immediately ready to be activated. Once
        /// activated the thread will execute tasks as they appear in the work item queue. Once the
        /// thread is blocked from executing tasks it will pass the ownership of the work item queue to another
        /// thread
        /// </summary>
        internal TaskWorkerThread(TaskExecutionModule parentModule, bool profileExecution)
        {
            this.parentModule = parentModule;

            // Initialize the data that only has to be set by the very first thread
            // created by the TEM
            this.exitTaskThreads        = new ManualResetEvent(false);
            this.exitTaskThreadsCache   = new ExitTaskCache(false);
            this.workerThreadQueue      = new Queue <TaskWorkerThread>();
            this.handleIdToWorkerThread = new Hashtable();
            this.workItemQueue          = new Queue <TaskExecutionState>();
            this.workItemInsertionEvent = new ManualResetEvent(false);
            this.waitingTasks           = new Hashtable();
            this.profileExecution       = profileExecution;

            InitializePerInstanceData();
        }
Example #7
0
        /// <summary>
        /// Default constructor.
        /// </summary>
        internal NodeManager(int cpuCount, bool childMode, Engine parentEngine)
        {
            nodeList      = new List <ProvidersNodeInformation>();
            nodeProviders = new List <INodeProvider>();

            this.parentEngine = parentEngine;

            this.statusMessageReceived = new ManualResetEvent(false);

            // Create the inproc node, this means that there will always be one node, node 0
            if (taskExecutionModule == null)
            {
                taskExecutionModule = new TaskExecutionModule(parentEngine.EngineCallback,
                                                              (cpuCount == 1 && !childMode ? TaskExecutionModule.TaskExecutionModuleMode.SingleProcMode :
                                                               TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode), parentEngine.ProfileBuild);
            }
        }
Example #8
0
        /// <summary>
        /// Default constructor.
        /// </summary>
        internal NodeManager(int cpuCount, bool childMode, Engine parentEngine)
        {
            nodeList = new List<ProvidersNodeInformation>();
            nodeProviders = new List<INodeProvider>();

            this.parentEngine = parentEngine;

            this.statusMessageReceived = new ManualResetEvent(false);

            // Create the inproc node, this means that there will always be one node, node 0
            if (taskExecutionModule == null)
            {
                taskExecutionModule = new TaskExecutionModule(parentEngine.EngineCallback,
                    (cpuCount == 1 && !childMode ? TaskExecutionModule.TaskExecutionModuleMode.SingleProcMode :
                                                   TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode), parentEngine.ProfileBuild);
            }
        }
Example #9
0
        /// <summary>
        /// This constructor creates a worker thread which is immediately ready to be activated. Once
        /// activated the thread will execute tasks as they appear in the work item queue. Once the
        /// thread is blocked from executing tasks it will pass the ownership of the work item queue to another
        /// thread
        /// </summary>
        internal TaskWorkerThread(TaskExecutionModule parentModule, bool profileExecution)
        {
            this.parentModule = parentModule;

            // Initialize the data that only has to be set by the very first thread
            // created by the TEM
            this.exitTaskThreads = new ManualResetEvent(false);
            this.exitTaskThreadsCache = new ExitTaskCache(false);
            this.workerThreadQueue = new Queue<TaskWorkerThread>();
            this.handleIdToWorkerThread = new Hashtable();
            this.workItemQueue = new Queue<TaskExecutionState>();
            this.workItemInsertionEvent = new ManualResetEvent(false);
            this.waitingTasks = new Hashtable();
            this.profileExecution = profileExecution;

            InitializePerInstanceData();
        }
Example #10
0
        /// <summary>
        /// Create an instance of this class to represent the IBuildEngine2 interface to the task
        /// including the event location where the log messages are raised
        /// </summary>
        /// <param name="parentModule">Parent Task Execution Module</param>
        /// <param name="handleId"></param>
        /// <param name="parentProjectFullFileName">the full path to the currently building project</param>
        /// <param name="projectFileOfTaskNode">the path to the actual file (project or targets) where the task invocation is located</param>
        /// <param name="loggingServices"></param>
        /// <param name="buildEventContext">Event Context where events will be seen to be raised from. Task messages will get this as their event context</param>
        internal EngineProxy
        (
            TaskExecutionModule parentModule,
            int handleId,
            string parentProjectFullFileName,
            string projectFileOfTaskNode,
            EngineLoggingServices loggingServices,
            BuildEventContext buildEventContext
        )
        {
            ErrorUtilities.VerifyThrow(parentModule != null, "No parent module.");
            ErrorUtilities.VerifyThrow(loggingServices != null, "No logging services.");
            ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need project file path string");

            this.parentModule = parentModule;
            this.handleId     = handleId;
            this.parentProjectFullFileName = parentProjectFullFileName;
            this.projectFileOfTaskNode     = projectFileOfTaskNode;
            this.loggingServices           = loggingServices;
            this.buildEventContext         = buildEventContext;
            this.callbackMonitor           = new object();

            activeProxy = true;
        }
Example #11
0
        public void TestTEMBatchSizeSettings()
        {
            Engine e = new Engine(@"C:\binpath");
            EngineLoggingServicesHelper loggingServicesHelper = new EngineLoggingServicesHelper();
            e.LoggingServices = loggingServicesHelper;
            EngineCallback engineCallback = new EngineCallback(e);
            Environment.SetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE", "-4");
            TaskExecutionModule TEM = new TaskExecutionModule(engineCallback, TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);
            DualQueue<BuildEventArgs> currentQueue = loggingServicesHelper.GetCurrentQueueBuildEvents();
            BuildEventArgs currentEvent = currentQueue.Dequeue();
            Assertion.Assert("Expected event to be a warning event", currentEvent is BuildWarningEventArgs);
            Assertion.Assert(String.Compare(ResourceUtilities.FormatResourceString("BatchRequestSizeOutOfRange", "-4"), ((BuildWarningEventArgs)currentEvent).Message, StringComparison.OrdinalIgnoreCase) == 0);

            e = new Engine(@"C:\binpath");
            loggingServicesHelper = new EngineLoggingServicesHelper();
            e.LoggingServices = loggingServicesHelper;
            engineCallback = new EngineCallback(e);
            Environment.SetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE", "0");
            TEM = new TaskExecutionModule(engineCallback, TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);
            currentQueue = loggingServicesHelper.GetCurrentQueueBuildEvents();
            currentEvent = currentQueue.Dequeue();
            Assertion.Assert("Expected event to be a warning event", currentEvent is BuildWarningEventArgs);
            Assertion.Assert(String.Compare(ResourceUtilities.FormatResourceString("BatchRequestSizeOutOfRange", "0"), ((BuildWarningEventArgs)currentEvent).Message, StringComparison.OrdinalIgnoreCase) == 0);

            e = new Engine(@"C:\binpath");
            loggingServicesHelper = new EngineLoggingServicesHelper();
            e.LoggingServices = loggingServicesHelper;
            engineCallback = new EngineCallback(e);
            Environment.SetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE", int.MaxValue.ToString());
            TEM = new TaskExecutionModule(engineCallback, TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);
            currentQueue = loggingServicesHelper.GetCurrentQueueBuildEvents();
            Assertion.Assert(currentQueue.Count == 0);

            e = new Engine(@"C:\binpath");
            loggingServicesHelper = new EngineLoggingServicesHelper();
            e.LoggingServices = loggingServicesHelper;
            engineCallback = new EngineCallback(e);
            Environment.SetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE", "4");
            TEM = new TaskExecutionModule(engineCallback, TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);
            currentQueue = loggingServicesHelper.GetCurrentQueueBuildEvents();
            Assertion.Assert(currentQueue.Count == 0);

            e = new Engine(@"C:\binpath");
            loggingServicesHelper = new EngineLoggingServicesHelper();
            e.LoggingServices = loggingServicesHelper;
            engineCallback = new EngineCallback(e);
            Environment.SetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE", "Giberish");
            TEM = new TaskExecutionModule(engineCallback, TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);
            currentQueue = loggingServicesHelper.GetCurrentQueueBuildEvents();
            currentEvent = currentQueue.Dequeue();
            Assertion.Assert("Expected event to be a warning event", currentEvent is BuildWarningEventArgs);
            Assertion.Assert(String.Compare(ResourceUtilities.FormatResourceString("BatchRequestSizeOutOfRange", "Giberish"), ((BuildWarningEventArgs)currentEvent).Message, StringComparison.OrdinalIgnoreCase) == 0);
        }
Example #12
0
        public MockTaskExecutionModule(EngineCallback nodeProxy, TaskExecutionModule.TaskExecutionModuleMode moduleMode)
            : base(nodeProxy, moduleMode, false)
        {

        }
Example #13
0
        /// <summary>
        /// Creates an instance of a MockTask, and returns the objects necessary to exercise
        /// taskEngine.InitializeTask
        /// </summary>
        /// <param name="taskNode"></param>
        /// <param name="taskEngine"></param>
        /// <param name="mockTask"></param>
        /// <param name="itemBucket"></param>
        /// <owner>RGoel</owner>
        private void InstantiateMockTaskHelper
            (
            XmlElement taskNode,
            out TaskEngine taskEngine,
            out MockTask mockTask,
            out ItemBucket itemBucket,
            out EngineProxy engineProxy,
            string condition
            )
        {
            LoadedType taskClass = new LoadedType(typeof(MockTask), new AssemblyLoadInfo(typeof(MockTask).Assembly.FullName, null));
            Engine engine = new Engine(@"c:\");
            Project project = new Project(engine);
            EngineCallback engineCallback = new EngineCallback(engine);
            TaskExecutionModule taskExecutionModule = new TaskExecutionModule(engineCallback, 
                                        TaskExecutionModule.TaskExecutionModuleMode.SingleProcMode, false);
            ProjectBuildState buildContext = new ProjectBuildState(null, null, new BuildEventContext(0, 1, 1, 1));
            int nodeProxyID = engineCallback.CreateTaskContext(project, null, buildContext, taskNode, EngineCallback.inProcNode, new BuildEventContext(BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId));
            taskEngine = new TaskEngine
                                (
                                    taskNode,
                                    null, /* host object */
                                    "In Memory",
                                    project.FullFileName,
                                    engine.LoggingServices,
                                    nodeProxyID,
                                    taskExecutionModule, 
                                    new BuildEventContext(0, 1, 1, 1)
                                );
            taskEngine.TaskClass = taskClass;
            engineProxy = new EngineProxy(taskExecutionModule, nodeProxyID, project.FullFileName, project.FullFileName, engine.LoggingServices, null);
            mockTask = new MockTask(new EngineProxy(taskExecutionModule, nodeProxyID, project.FullFileName, project.FullFileName, engine.LoggingServices, null));

            // The code below creates an item table that is equivalent to the following MSBuild syntax:
            //
            //      <ItemGroup>
            //          <ItemListContainingOneItem Include="a.cs">
            //              <Culture>fr-fr</Culture>
            //          </ItemListContainingOneItem>
            //
            //          <ItemListContainingTwoItems Include="b.cs">
            //              <HintPath>c:\foo</HintPath>
            //          </ItemListContainingTwoItems>
            //          <ItemListContainingTwoItems Include="c.cs">
            //              <HintPath>c:\bar</HintPath>
            //          </ItemListContainingTwoItems>
            //      </ItemGroup>
            //
            Hashtable itemsByName = new Hashtable(StringComparer.OrdinalIgnoreCase);

            BuildItemGroup itemListContainingOneItem = new BuildItemGroup();
            BuildItem a = itemListContainingOneItem.AddNewItem("ItemListContainingOneItem", "a.cs");
            a.SetMetadata("Culture", "fr-fr");
            itemsByName["ItemListContainingOneItem"] = itemListContainingOneItem;

            BuildItemGroup itemListContainingTwoItems = new BuildItemGroup();
            BuildItem b = itemListContainingTwoItems.AddNewItem("ItemListContainingTwoItems", "b.cs");
            b.SetMetadata("HintPath", "c:\\foo");
            BuildItem c = itemListContainingTwoItems.AddNewItem("ItemListContainingTwoItems", "c.cs");
            c.SetMetadata("HintPath", "c:\\bar");
            itemsByName["ItemListContainingTwoItems"] = itemListContainingTwoItems;

            itemBucket = new ItemBucket(new string[0], new Dictionary<string, string>(), LookupHelpers.CreateLookup(itemsByName), 0);
        }
Example #14
0
        /// <summary>
        /// Indicates to the EngineProxy that it is no longer needed.
        /// Called by TaskEngine when the task using the EngineProxy is done.
        /// </summary>
        internal void MarkAsInActive()
        {
            activeProxy = false;

            // Since the task has a pointer to this class it may store it in a static field. Null out
            // internal data so the leak of this object doesn't lead to a major memory leak.
            loggingServices = null;
            parentModule = null;
            buildEventContext = null;
            
            // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done)
            // this will be null if the engineproxy was never sent accross an appdomain boundry.
            if (sponsor != null)
            {
                ILease lease = (ILease)RemotingServices.GetLifetimeService(this);
             
                if (lease != null)
                {
                    lease.Unregister(sponsor);
                }
                
                sponsor.Close();
                sponsor = null;
            }
        }
Example #15
0
        /// <summary>
        /// Create an instance of this class to represent the IBuildEngine2 interface to the task
        /// including the event location where the log messages are raised
        /// </summary>
        /// <param name="parentModule">Parent Task Execution Module</param>
        /// <param name="handleId"></param>
        /// <param name="parentProjectFullFileName">the full path to the currently building project</param>
        /// <param name="projectFileOfTaskNode">the path to the actual file (project or targets) where the task invocation is located</param>
        /// <param name="loggingServices"></param>
        /// <param name="buildEventContext">Event Context where events will be seen to be raised from. Task messages will get this as their event context</param>
        internal EngineProxy
        (
            TaskExecutionModule parentModule, 
            int handleId, 
            string parentProjectFullFileName,
            string projectFileOfTaskNode, 
            EngineLoggingServices loggingServices,
            BuildEventContext buildEventContext
        )
        {
            ErrorUtilities.VerifyThrow(parentModule != null, "No parent module.");
            ErrorUtilities.VerifyThrow(loggingServices != null, "No logging services.");
            ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need project file path string");

            this.parentModule = parentModule;
            this.handleId = handleId;
            this.parentProjectFullFileName = parentProjectFullFileName;
            this.projectFileOfTaskNode = projectFileOfTaskNode;
            this.loggingServices = loggingServices;
            this.buildEventContext = buildEventContext;
            this.callbackMonitor = new object();

            activeProxy = true;
        }
Example #16
0
        public void TaskWorkerThreadTest()
        {
            // This event will be triggered right before a "engine" call back is made. 
            // Once this event is fired we insert another item into the queue
            ManualResetEvent rightBeforeCallbackBlock = new ManualResetEvent(false);

            engine = new Engine(@"c:\");
            TaskExecutionModule TEM = new TaskExecutionModule(new EngineCallback(engine), TaskExecutionModule.TaskExecutionModuleMode.MultiProcFullNodeMode, false);

            // Create a worker thread and make it the active node thread
            TaskWorkerThread workerThread = TEM.GetWorkerThread();

            // Get some tasks which we can then provide execution methods to
            List<TaskExecutionStateHelper> tasks = InitializeTaskState();

            tasks[1].ExecutionTaskDelegateParameter = rightBeforeCallbackBlock;
            tasks[1].ExecuteDelegate = delegate(object parameter)
            {
                ((ManualResetEvent)parameter).Set();

                workerThread.WaitForResults(tasks[1].HandleId, new BuildResult[] { new BuildResult(null, new Hashtable(StringComparer.OrdinalIgnoreCase), true, tasks[1].HandleId, 0, 2, false, string.Empty, string.Empty, 0, 0, 0) }, new BuildRequest[1]);
            };

            // Task 0 will cause a baseActiveThread to start up and run
            workerThread.PostWorkItem(tasks[0]);

            // Since this will do a callback and will generate a waitingActiveThread
            workerThread.PostWorkItem(tasks[1]);

            workerThread.ActivateThread();

            // Wait for the call back to happen     
            rightBeforeCallbackBlock.WaitOne();

            // Lets insert a execution task which and post a work item which will cause a localDoneEvent to be set
            tasks[2].ExecutionTaskDelegateParameter = null;
            tasks[2].ExecuteDelegate = null;
            //  TaskWorkerThread.PostBuildResult(new BuildResult(null, true, tasks[2].NodeProxyId, 0));
            workerThread.PostWorkItem(tasks[2]);

            //Post a build Result while one of the threads is waiting active, this should cause us to reuse the first thread
            workerThread.PostBuildResult(new BuildResult(null, new Hashtable(StringComparer.OrdinalIgnoreCase), true, tasks[2].HandleId, 0, 2, false, string.Empty, string.Empty, 0, 0, 0));

            tasks[3].ExecutionTaskDelegateParameter = null;
            tasks[3].ExecuteDelegate = null;

            workerThread.PostWorkItem(tasks[3]);
            TEM.Shutdown();

            // Count up the number of threads used during the execution of the tasks
            List<int> threadsUsedForExecution = new List<int>();
            foreach (TaskExecutionStateHelper state in tasks)
            {
                // If the list does not contain the threadId add it to the list
                if (!threadsUsedForExecution.Contains(state.ThreadId))
                {
                    threadsUsedForExecution.Add(state.ThreadId);
                }
            }
            // Make sure we use less threads then the number of sumbitted tasks which would indicate that threads are reused
            Assert.IsTrue(threadsUsedForExecution.Count < tasks.Count, "Expected for the number of unique threads to be less than the number of tasks as threads should have been reused");
        }