public void QueueWorkItem([NotNull, Pooled] Action workItem) { // Throw right here to help debugging if (workItem == null) { throw new NullReferenceException(nameof(workItem)); } PooledDelegateHelper.AddReference(workItem); LinkedWork newSharedNode = null; while (true) { // Are all threads busy ? LinkedIdleThread node = idleThreads; if (node == null) { if (newSharedNode == null) { newSharedNode = new LinkedWork(workItem); if (idleThreads != null) { continue; } } // Schedule it on the shared stack newSharedNode.Previous = Interlocked.Exchange(ref sharedWorkStack, newSharedNode); newSharedNode.PreviousIsValid = true; break; } // Schedule this work item on latest idle thread while (node.PreviousIsValid == false) { // Spin while invalid, should be extremely short } // Try take this thread if (Interlocked.CompareExchange(ref idleThreads, node.Previous, node) != node) { continue; // Latest idle threads changed, try again } // Wakeup thread and schedule work // The order those two lines are laid out in is essential ! Interlocked.Exchange(ref node.Work, workItem); node.MRE.Set(); break; } }
/// <summary> /// Attempt to remove the latest action scheduled on the shared stack, /// returns work only if there was any work AND the item was successfully /// removed from the stack without having to block. /// </summary> bool TryTakeFromSharedNonBlocking(out Action a, LinkedWork nodeToProcess) { if (nodeToProcess != null) { while (nodeToProcess.PreviousIsValid == false) { // Spin while invalid, should be extremely short } if (Interlocked.CompareExchange(ref sharedWorkStack, nodeToProcess.Previous, nodeToProcess) == nodeToProcess) { a = nodeToProcess.Work; return(true); } } a = null; return(false); }
private void ProcessWorkItems(object nodeObj) { // nodeObj is non-null when a thread caught an exception and had to throw, // the thread created another one and passed its node obj to us. LinkedIdleThread node = nodeObj == null ? new LinkedIdleThread(new ManualResetEventSlim(true)) : (LinkedIdleThread)nodeObj; try { while (true) { Action action; LinkedWork workNode = sharedWorkStack; if (workNode != null) { if (TryTakeFromSharedNonBlocking(out var tempAction, workNode)) { action = tempAction; } else { // We have shared work to do but failed to retrieve it, try again continue; } } else { // Should we notify system that this thread is ready to work? // This has to also work for when a thread takes the place of another one when restoring // from an exception for example. // If the mre was set and we took the work, this node definitely is dequeued, re-queue it if (node.MRE.IsSet && Volatile.Read(ref node.Work) == null) { // Notify that we're waiting for work node.MRE.Reset(); node.PreviousIsValid = false; node.Previous = Interlocked.Exchange(ref idleThreads, node); node.PreviousIsValid = true; } // Wait for work SpinWait sw = new SpinWait(); while (true) { if (node.MRE.IsSet) { // Work has been scheduled for this thread specifically, take it action = Interlocked.Exchange(ref node.Work, null); break; } if (TryTakeFromSharedNonBlocking(out var tempAction, sharedWorkStack)) { action = tempAction; break; // We successfully dequeued this node from the shared stack, quit loop and process action } // Wait for work if (sw.NextSpinWillYield) { // Wait for work to be scheduled specifically to this thread node.MRE.Wait(); action = Interlocked.Exchange(ref node.Work, null); break; } sw.SpinOnce(); } } try { action(); } finally { PooledDelegateHelper.Release(action); } } } finally { // We must keep up the amount of threads that the system handles. // Spawn a new one as this one is about to abort because of an exception. NewThread(node); } }