/// <summary> /// Raises the WorkerException event. /// TODO: Write to the event log if no exception handlers are attached? /// </summary> private void OnException(ThreadPoolWorkItem workItem, Exception e) { ThreadPoolExceptionHandler eh; lock (_eventLock) { eh = WorkerException; } if (eh == null) { return; } Delegate[] delegates = eh.GetInvocationList(); bool handled = false; foreach (ThreadPoolExceptionHandler d in delegates.OfType <ThreadPoolExceptionHandler>()) { d(this, workItem, e, ref handled); if (handled) { return; } } }
/// <summary> /// Raises the AfterWorkItem event /// </summary> private void OnAfterWorkItem(ThreadPoolWorkItem workItem) { AfterWorkItemHandler delegateToFire; lock (_eventLock) { delegateToFire = AfterWorkItem; } delegateToFire?.Invoke(this, workItem); }
/// <summary> /// Adds a work item to the queue and potentially start a new thread. /// A thread is started if there are no idle threads or if there is already /// something on the queue - but in each case, only if the total number of /// threads is less than the maximum. /// </summary> /// <param name="workItem">The actual work item to add to the queue.</param> public void AddWorkItem([NotNull] ThreadPoolWorkItem workItem) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } bool startNewThread; lock (_stateLock) { lock (_queueLock) { if (_queue.Count == 0) { _queue.Enqueue(workItem); } else { // Work out where in the queue the item should go // Common case: it belongs at the end if (_queue[_queue.Count - 1].Priority >= workItem.Priority) { _queue.Enqueue(workItem); } else { // This will find the complement of the correct position, due to the // "interesting" nature of PriorityComparer. int position = _queue.BinarySearch(workItem, PriorityComparer.Instance); _queue.Enqueue(workItem, ~position); } } startNewThread = WorkingThreads + _queue.Count > TotalThreads && TotalThreads < MaxThreads; // Always pulse the queueLock, whether there's something waiting or not. // This is easier than trying to work out for sure whether or not it's // worth pulsing, and errs on the side of caution. Monitor.Pulse(_queueLock); } } if (startNewThread) { StartWorkerThread(); } }
/// <summary> /// Main worker thread loop. This picks jobs off the queue and executes /// them, until it's time to die. /// </summary> private void WorkerThreadLoop() { // Big try/finally block just to decrement the number of threads whatever happens. try { DateTime lastJob = DateTime.UtcNow; while (true) { lock (_stateLock) { if (TotalThreads > MaxThreads) { return; } } int waitPeriod = CalculateWaitPeriod(lastJob); ThreadPoolWorkItem job = GetNextWorkItem(waitPeriod); // No job? Check whether or not we should die if (job == null) { if (CheckIfThreadShouldQuit(lastJob)) { return; } } else { ExecuteWorkItem(job); lastJob = DateTime.UtcNow; } } } finally { OnWorkerThreadExit(); } }
/// <summary> /// Executes the given work item, firing the BeforeWorkItem and AfterWorkItem events, /// and incrementing and decrementing the number of working threads. /// </summary> /// <param name="job">The work item to execute</param> private void ExecuteWorkItem([NotNull] ThreadPoolWorkItem job) { lock (_stateLock) { Interlocked.Increment(ref _workingThreads); Thread.CurrentThread.Priority = _workerThreadPriority; Thread.CurrentThread.IsBackground = _workerThreadsAreBackground; } try { OnBeforeWorkItem(job, out bool cancel); if (cancel) { return; } try { job.Invoke(); } catch (Exception e) { OnException(job, e); return; } OnAfterWorkItem(job); } finally { lock (_stateLock) { Thread.CurrentThread.Priority = _workerThreadPriority; Thread.CurrentThread.IsBackground = _workerThreadsAreBackground; Interlocked.Decrement(ref _workingThreads); } } }
/// <summary> /// Cancels the first work item with the specified ID, if there is one. /// Note that items which have been taken off the queue and are running /// or about to be started cannot be cancelled. /// </summary> /// <param name="id">The ID of the work item to cancel</param> public bool CancelWorkItem([NotNull] object id) { if (id == null) { throw new ArgumentNullException(nameof(id)); } lock (_queueLock) { for (int i = 0; i < _queue.Count; i++) { ThreadPoolWorkItem item = _queue[i]; object otherID = item.ID; if (otherID != null && id.Equals(otherID)) { _queue.RemoveAt(i); return(true); } } } return(false); }
/// <summary> /// Raises the BeforeWorkItem event /// </summary> /// <param name="workItem">The work item which is about to execute</param> /// <param name="cancel">Whether or not the work item was cancelled by an event handler</param> private void OnBeforeWorkItem(ThreadPoolWorkItem workItem, out bool cancel) { cancel = false; BeforeWorkItemHandler delegateToFire; lock (_eventLock) { delegateToFire = BeforeWorkItem; } if (delegateToFire != null) { Delegate[] delegates = delegateToFire.GetInvocationList(); foreach (BeforeWorkItemHandler d in delegates.OfType <BeforeWorkItemHandler>()) { d(this, workItem, ref cancel); if (cancel) { return; } } } }