/// <summary>
        /// Determines the maximum number of actions to execute in parallel, taking into account the resources available on this machine.
        /// </summary>
        /// <returns>Max number of actions to execute in parallel</returns>
        public virtual int GetMaxActionsToExecuteInParallel()
        {
            // Get the number of logical processors
            int NumLogicalCores = Utils.GetLogicalProcessorCount();

            // Use WMI to figure out physical cores, excluding hyper threading.
            int NumPhysicalCores = Utils.GetPhysicalProcessorCount();

            if (NumPhysicalCores == -1)
            {
                NumPhysicalCores = NumLogicalCores;
            }

            // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls.
            int MaxActionsToExecuteInParallel = 0;

            if (NumPhysicalCores < NumLogicalCores && ProcessorCountMultiplier != 1.0)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use multiplier if provided
                MaxActionsToExecuteInParallel = (int)(NumPhysicalCores * ProcessorCountMultiplier);
            }
            else if (NumPhysicalCores < NumLogicalCores && NumPhysicalCores > 4)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use average of logical and physical if we have "lots of cores"
                MaxActionsToExecuteInParallel = Math.Max((int)(NumPhysicalCores + NumLogicalCores) / 2, NumLogicalCores - 4);
            }
            // No hyper-threading. Only kicking off a task per CPU to keep machine responsive.
            else
            {
                MaxActionsToExecuteInParallel = NumPhysicalCores;
            }

#if !NET_CORE
            if (Utils.IsRunningOnMono)
            {
                long PhysicalRAMAvailableMB = (new PerformanceCounter("Mono Memory", "Total Physical Memory").RawValue) / (1024 * 1024);
                // heuristic: give each action at least 1.5GB of RAM (some clang instances will need more) if the total RAM is low, or 1GB on 16+GB machines
                long MinMemoryPerActionMB       = (PhysicalRAMAvailableMB < 16384) ? 3 * 1024 / 2 : 1024;
                int  MaxActionsAffordedByMemory = (int)(Math.Max(1, (PhysicalRAMAvailableMB) / MinMemoryPerActionMB));

                MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxActionsAffordedByMemory);
            }
#endif

            MaxActionsToExecuteInParallel = Math.Max(1, Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount));
            return(MaxActionsToExecuteInParallel);
        }