/// <summary> /// Raises the AfterWorkItem event /// </summary> void OnAfterWorkItem(ThreadPoolWorkItem workItem) { AfterWorkItemHandler delegateToFire; lock (eventLock) { delegateToFire = afterWorkItem; } if (delegateToFire != null) { delegateToFire(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(ThreadPoolWorkItem workItem) { if (workItem == null) { throw new ArgumentNullException("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 (((ThreadPoolWorkItem)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> /// 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(object id) { if (id == null) { throw new ArgumentNullException("id"); } lock (queueLock) { for (int i = 0; i < queue.Count; i++) { ThreadPoolWorkItem item = (ThreadPoolWorkItem)queue[i]; object otherID = item.ID; if (otherID != null && id.Equals(otherID)) { queue.RemoveAt(i); return(true); } } } return(false); }
/// <summary> /// Main worker thread loop. This picks jobs off the queue and executes /// them, until it's time to die. /// </summary> 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> /// 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> 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) { d(this, workItem, ref cancel); if (cancel) { return; } } } }
/// <summary> /// Raises the WorkerException event. /// TODO: Write to the event log if no exception handlers are attached? /// </summary> void OnException(ThreadPoolWorkItem workItem, Exception e) { ThreadPoolExceptionHandler eh; lock (eventLock) { eh = exceptionHandler; } if (eh != null) { Delegate[] delegates = eh.GetInvocationList(); bool handled = false; foreach (ThreadPoolExceptionHandler d in delegates) { d(this, workItem, e, ref handled); if (handled) { return; } } } }
/// <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> void ExecuteWorkItem(ThreadPoolWorkItem job) { lock (stateLock) { workingThreads++; Thread.CurrentThread.Priority = workerThreadPriority; Thread.CurrentThread.IsBackground = workerThreadsAreBackground; } try { bool cancel; OnBeforeWorkItem(job, out 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; workingThreads--; } } }