// // Constructor: registers a take with a blocking queue. // internal StRegisteredTake(StBlockingQueue <T> queue, StTakeCallback <T> callback, object cbState, int timeout, bool executeOnce) { // // Validate the arguments. // if (timeout == 0) { throw new ArgumentOutOfRangeException("\"timeout\" can not be zero"); } if (callback == null) { throw new ArgumentOutOfRangeException("\"callback\" must be specified"); } // // Initialize the registered take fields. // this.queue = queue; cbparker = new CbParker(UnparkCallback); if ((this.timeout = timeout) != Timeout.Infinite) { toTimer = new RawTimer(cbparker); } this.executeOnce = executeOnce; this.callback = callback; this.cbState = cbState; // // Execute the TryTakePrologue prologue on the queue. // waitNode = queue.TryTakePrologue(cbparker, StParkStatus.Success, out dataItem, ref hint); // // Set the state to active and enable the unpark callback. // state = ACTIVE; int ws; if ((ws = cbparker.EnableCallback(timeout, toTimer)) != StParkStatus.Pending) { // // The take operation was already accomplished. To prevent // uncontrolled reentrancy, the unpark callback is executed inline. // UnparkCallback(ws); } }
// // Executes the unpark callback. // private void UnparkCallback(int ws) { Debug.Assert(ws == StParkStatus.Success || ws == StParkStatus.Timeout || ws == StParkStatus.TakeCancelled); // // If the registered take was cancelled, cancel the take attempt // and return immediately. // if (ws == StParkStatus.TakeCancelled) { queue.CancelTakeAttempt(waitNode, hint); return; } // // Set state to *our busy*, grabing the current state. // StParker myBusy = new SentinelParker(); StParker oldState = Interlocked.Exchange <StParker>(ref state, myBusy); do { // // If the take operation succeeded, execute the take epilogue; // otherwise, cancel the take attempt. // if (ws == StParkStatus.Success) { queue.TakeEpilogue(); } else { queue.CancelTakeAttempt(waitNode, hint); } // // Execute the user callback routine. // cbtid = Thread.CurrentThread.ManagedThreadId; callback(cbState, waitNode == null ? dataItem : waitNode.channel, ws == StParkStatus.Timeout); cbtid = 0; // // If the registered take was configured to execute once or // there is an unregister in progress, set the state to INACTIVE. // If a thread is waiting to unregister, unpark it. // if (executeOnce || !(oldState is SentinelParker)) { if (!(oldState is SentinelParker)) { oldState.Unpark(StParkStatus.Success); } else { state = INACTIVE; } return; } // // We must re-register with the queue. // So, initialize the parker and execute the TakeAny prologue. // cbparker.Reset(); waitNode = queue.TryTakePrologue(cbparker, StParkStatus.Success, out dataItem, ref hint); // // Enable the unpark callback. // if ((ws = cbparker.EnableCallback(timeout, toTimer)) == StParkStatus.Pending) { // // If the *state* field is still *my busy* set it to ACTIVE; // anyway, return. // if (state == myBusy) { Interlocked.CompareExchange <StParker>(ref state, ACTIVE, myBusy); } return; } // // The take was already accomplished; so, to prevent uncontrolled // reentrancy execute the unpark callback inline. // } while (true); }
// // Cancels the specifie add attempt. // internal override void CancelAddAttempt(StBlockingQueue<T>.WaitNode wn, StBlockingQueue<T>.WaitNode hint) { waitQueue.Unlink(wn, hint); }
// // Tries to take a data item from the specified subset of queues, // defined by offset and count, activating the specified cancellers. // public static int TryTakeAny(StBlockingQueue <T>[] qs, int offset, int count, out T di, StCancelArgs cargs) { int def = 0; int len = qs.Length; // // Try to take a data item immediately from one of the specified queues. // for (int j = offset, i = 0; i < count; i++) { StBlockingQueue <T> q = qs[j]; if (q != null) { if (q.TryTake(out di)) { return(StParkStatus.Success + j); } def++; } if (++j >= len) { j = 0; } } // // If the *qs* array contains only null references, throw the // ArgumentException. // if (def == 0) { throw new ArgumentException("qs: array contains only null references"); } // // None of the specified queues allows an immediate take operation. // So, return failure if a null timeout was specified. // di = default(T); if (cargs.Timeout == 0) { return(StParkStatus.Timeout); } // // Create a parker and execute the take-any prologue on the // queues; the loop is exited when we detect that the take-any // operation was satisfied. // StParker pk = new StParker(); WaitNode[] wns = new WaitNode[len]; WaitNode[] hints = new WaitNode[len]; int lv = -1; for (int j = offset, i = 0; !pk.IsLocked && i < count; i++) { StBlockingQueue <T> q = qs[j]; if (q != null) { if ((wns[j] = q.TryTakePrologue(pk, (StParkStatus.Success + j), out di, ref hints[j])) == null) { break; } lv = j; } if (++j >= len) { j = 0; } } // // Park the current thread, activating the specified cancellers. // int ws = pk.Park(cargs); // // If the take-any operation succeed, compute the index of the // queue where we the data item was taken, retrive the data item // and execute the take epilogue, if needed. // int ti = -1; if (ws >= StParkStatus.Success) { ti = ws - StParkStatus.Success; if (wns[ti] != null) { di = wns[ti].channel; qs[ti].TakeEpilogue(); } } // // Cancel the take attempt on all queues where we inserted // wait blocks, except the one where we retrieved the data item. // for (int j = offset, i = 0; i < count; i++) { WaitNode wn; if (j != ti && (wn = wns[j]) != null) { qs[j].CancelTakeAttempt(wn, hints[j]); } if (j == lv) { break; } if (++j >= len) { j = 0; } } // // Return success or failure appropriately. // if (ti != -1) { return(ws); } StCancelArgs.ThrowIfException(ws); return(StParkStatus.Timeout); }