/// <summary> /// Runs an action asynchronously. No assumption is made about the identity under which the action is run. /// </summary> /// <param name="action">The action to perform asynchronously.</param> /// <param name="exceptionCallback">An optional second action to call if the first action throws an exception.</param> /// <returns><b>true</b> if the item was scheduled to run asynchronously, or <b>false</b> if no worker threads were available and the operation was run synchronously.</returns> /// <remarks>Does not throw if the specified action throws an exception.</remarks> public bool RunAsync(Action work, Action <Exception> exceptionCallback = null) { Retry: bool synchronous; // try to get a ready thread Worker worker = _readyWorkerList.Pop(); System.Diagnostics.Debug.Assert(worker == null || !worker.IsBusy); // no ready workers and not too many workers already? if ((synchronous = (worker == null))) { // too many busy workers already? if (_busyWorkers > MaxWorkerThreads) { // just wait for things to change--we're overloaded and we're not going to make things better by starting up more threads! System.Threading.Thread.Sleep(100); goto Retry; } // create a new one right now--it will be added to the ready worker list when we're done worker = CreateWorker(); // record this miss System.Threading.Interlocked.Exchange(ref _lastSynchronousExecutionTicks, Environment.TickCount); // wake the pool master immediately _wakePoolMasterThread.Set(); } worker.RunAsync( delegate() { // we now have a worker that is busy System.Threading.Interlocked.Increment(ref _busyWorkers); // keep track of the maximum concurrent usage InterlockedHelper.Max(ref _peakConcurrentUsageSinceLastRetirementCheck, _busyWorkers); try { work(); } catch (Exception ex) { if (exceptionCallback != null) { exceptionCallback(ex); } else { Program.Error("Exception running '" + _poolName + "' pool async operation with no exception handler", ex.ToString()); } } finally { // the worker is no longer busy System.Threading.Interlocked.Decrement(ref _busyWorkers); } }); return(!synchronous); }
private void PoolMaster() { RunWithLogException( () => { int lastRetirementTicks = Environment.TickCount; int lastCreationTicks = Environment.TickCount; // loop forever (we're a background thread, so if the process exits, no problem!) for (bool stop = false; !stop;) { RunWithLogException( () => { // get the wakt pool master signal first before we check to see if we've been disposed ManualResetEvent wakePoolMasterThread = _wakePoolMasterThread; // are we being disposed of? if (_disposing != 0) { // exit now! stop = true; return; } // sleep for up to one second or until we are awakened if (wakePoolMasterThread.WaitOne(1000)) { wakePoolMasterThread.Reset(); } // are we being disposed of? if (_disposing != 0) { // exit now! stop = true; return; } // do we need to add more workers? int totalWorkers = _workers; int readyWorkers = _readyWorkerList.Count; int busyWorkers = _busyWorkers; if (readyWorkers <= Math.Min(1, PoolChunkSize / 2)) { // too many workers already? if (totalWorkers > MaxWorkerThreads) { // have we NOT already logged that we are using an insane number of threads in the past 60 minutes? int now = Environment.TickCount; int lastWarning = _highThreadsWarningTickCount; if (TimeElapsed(lastWarning, now) > HighThreadCountWarningEnvironmentTicks) { // race to log a warning--did we win the race? if (System.Threading.Interlocked.CompareExchange(ref _highThreadsWarningTickCount, now, lastWarning) == lastWarning) { Program.LogVerbose("'" + _poolName + "' Worker Pool Warning: There are already " + (readyWorkers + busyWorkers).ToString() + " workers in this pool. Given the number of CPUs on this computer, workers are no longer added after around " + MaxWorkerThreads.ToString() + "!"); } } // now we will just carry on because we will not expand beyond the number of threads we have now } else { // initialize some workers for (int i = 0; i < PoolChunkSize; ++i) { // create a new worker Worker worker = CreateWorker(); System.Diagnostics.Debug.Assert(!worker.IsBusy); _readyWorkerList.Push(worker); } // update the high water mark if needed InterlockedHelper.Max(ref _workersHighWaterMark, _workers); System.Diagnostics.Debug.WriteLine(_poolName + " workers:" + _workers.ToString()); // record the expansion time so we don't retire anything for at least a minute lastCreationTicks = Environment.TickCount; } } // enough ready workers that we could get by with less else if (readyWorkers > Math.Max(3, PoolChunkSize * 2)) { int ticksNow = Environment.TickCount; int ticksSinceCreation = TimeElapsed(lastCreationTicks, ticksNow); int ticksSinceSynchronousExecution = TimeElapsed(_lastSynchronousExecutionTicks, ticksNow); int ticksSinceRetirement = TimeElapsed(lastRetirementTicks, ticksNow); bool retireThreadsImmediately = !_retireThreads.WaitOne(0); // haven't had a synchronous execution or new worker creation or removed any workers for a while? if (retireThreadsImmediately || ( ticksSinceCreation > RetireCheckAfterCreationMilliseconds && ticksSinceSynchronousExecution > RetireCheckAfterCreationMilliseconds && ticksSinceRetirement > RetireCheckMilliseconds )) { // was the highest concurrency since the last time we checked such that we didn't need many of the threads? if (retireThreadsImmediately || _peakConcurrentUsageSinceLastRetirementCheck < Math.Max(1, totalWorkers - PoolChunkSize)) { // remove some workers for (int i = 0; i < PoolChunkSize; ++i) { // retire one worker--did we remove too many (this could happen if there was a sudden upsurge in worker need while we were stopping workers) if (!RetireOneWorker()) { // bail out now--we removed too many (this could happen if there was a sudden upsurge in worker need while we were stopping workers) break; } } System.Diagnostics.Debug.WriteLine(_poolName + " workers:" + _workers.ToString()); // record when we retired threads lastRetirementTicks = Environment.TickCount; } // reset the concurrency System.Threading.Interlocked.Exchange(ref _peakConcurrentUsageSinceLastRetirementCheck, 0); } } }, "'{0}' Pool Master Exception"); } }, "Critical '{0}' Pool Master Exception"); }
private void WorkerFunc() { try { RunWithLogException( () => { long maxInvokeTicks = 0; System.Diagnostics.Debug.WriteLine("Starting " + _id); long startTicks = WorkerPool.Ticks; long completionTicks = startTicks; // loop until we're told to stop while (_stop == 0) { startTicks = WorkerPool.Ticks; completionTicks = startTicks; // wait for the wake thread event to be signalled so we can start some work _pool.WaitForWork(_wakeThread); // stop now? if (_stop != 0) { break; } // record the start time startTicks = WorkerPool.Ticks; // NO work to execute? (just in case--I don't think this can ever really happen) if (_actionToPerform == null) { // nothing to do, so we're done completionTicks = WorkerPool.Ticks; } else { RunWithLogException( () => { // perform the work _actionToPerform.DynamicInvoke(); }, "An unexpected error occured during a '{0}' thread worker dynamic invocation"); // mark the time completionTicks = WorkerPool.Ticks; } // finish work in succes case FinishWork(); // record statistics long invokeTime = _invokeTicks; // ignore how long it took if we never got invoked if (invokeTime > 0) { long invokeTicks = startTicks - invokeTime; long executionTicks = completionTicks - startTicks; if (invokeTicks > maxInvokeTicks) { maxInvokeTicks = invokeTicks; InterlockedHelper.Max(ref sSlowestInvocation, invokeTicks); } } } System.Diagnostics.Debug.WriteLine("Exiting '" + _id + "' worker thread: max invocation wait=" + (maxInvokeTicks * 1000.0f / WorkerPool.TicksPerSecond).ToString() + "ms"); }, "Exception in '{0}' WorkerFunc"); } catch (OutOfMemoryException e) { Program.RecordAsyncException(e); } finally { // wait for up to one second for the stopper to be done accessing us _allowDisposal.WaitOne(1000); Dispose(); } }