private void InvokeContinuation() { Debug.Assert(_continuation != null); var continuation = _continuation !; switch (_capturedContext) { case null: if (RunContinuationsAsynchronously) { #if TARGETS_NET || TARGETS_NETCORE || GREATERTHAN_NETSTANDARD13 if (_executionContext != null) { ThreadPoolEx.QueueUserWorkItem(continuation, _continuationState, true); return; } #endif ThreadPoolEx.QueueUserWorkItem(continuation, _continuationState, true); return; } continuation(_continuationState); return; case SynchronizationContext sc: sc.Post(s => { var state = (Tuple <Action <object?>, object?>)s !; state.Item1(state.Item2); }, Tuple.Create(continuation, _continuationState)); return; case TaskScheduler ts: #if NET40 Task.Factory.StartNew(continuation, _continuationState, CancellationToken.None, TaskCreationOptions.None, ts); #else Task.Factory.StartNew(continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); #endif return; default: break; } }
/// <summary>Schedules the continuation action for this operation.</summary> /// <param name="continuation">The continuation to invoke when the operation has completed.</param> /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> /// <param name="flags">The flags describing the behavior of the continuation.</param> public void OnCompleted(Action <object?> continuation, object?state, short token, ValueTaskSourceOnCompletedFlags flags) { if (continuation == null) { throw new ArgumentNullException(nameof(continuation)); } ValidateToken(token); if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0) { #if TARGETS_NET || TARGETS_NETCORE || GREATERTHAN_NETSTANDARD13 _executionContext = ExecutionContext.Capture(); #endif } if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0) { var sc = SynchronizationContext.Current; if (sc != null && sc.GetType() != typeof(SynchronizationContext)) { _capturedContext = sc; } else { var ts = TaskScheduler.Current; if (ts != TaskScheduler.Default) { _capturedContext = ts; } } } // We need to set the continuation state before we swap in the delegate, so that // if there's a race between this and SetResult/Exception and SetResult/Exception // sees the _continuation as non-null, it'll be able to invoke it with the state // stored here. However, this also means that if this is used incorrectly (e.g. // awaited twice concurrently), _continuationState might get erroneously overwritten. // To minimize the chances of that, we check preemptively whether _continuation // is already set to something other than the completion sentinel. object?oldContinuation = _continuation; if (oldContinuation == null) { _continuationState = state; oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null); } if (oldContinuation == null) { return; } // Operation already completed, so we need to queue the supplied callback. if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.Sentinel)) { throw new InvalidOperationException(); } switch (_capturedContext) { case null: #if TARGETS_NET || TARGETS_NETCORE || GREATERTHAN_NETSTANDARD13 if (_executionContext != null) { ThreadPoolEx.QueueUserWorkItem(continuation, state, true); return; } #endif ThreadPoolEx.UnsafeQueueUserWorkItem(continuation, state, true); return; case SynchronizationContext sc: sc.Post(s => { var tuple = (Tuple <Action <object?>, object?>)s !; tuple.Item1(tuple.Item2); }, Tuple.Create(continuation, state)); return; case TaskScheduler ts: #if NET40 Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.None, ts); #else Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); #endif return; } }
/// <summary> /// The main processing loop. /// </summary> public void Run() { bool lastAcquireFailed = false; while (!_halted) { try { // check if we're supposed to pause... lock (_pauseLock) { while (_paused && !_halted) { try { // wait until togglePause(false) is called... Monitor.Wait(_pauseLock, 100); } catch (ThreadInterruptedException) { } } if (_halted) { break; } } int availTreadCount = _threadPool.AvailableThreads; DateTime now; int spinInterval; int numPauses; if (availTreadCount > 0) { Trigger trigger = null; now = DateTime.UtcNow; _signaled = false; try { trigger = AcquireNextTrigger(now.AddMilliseconds(_idleWaitTime)); lastAcquireFailed = false; } catch (Exception e) { if (!lastAcquireFailed) { log.Error("SchedulerThreadLoop: RuntimeException " + e.Message, e); } lastAcquireFailed = true; } if (trigger != null) { now = DateTime.UtcNow; DateTime triggerTime = trigger.GetNextFireTimeUtc().Value; long timeUntilTrigger = (long)(triggerTime - now).TotalMilliseconds; spinInterval = 10; // this looping may seem a bit silly, but it's the // current work-around // for a dead-lock that can occur if the Thread.sleep() // is replaced with // a obj.wait() that gets notified when the signal is // set... // so to be able to detect the signal change without // sleeping the entire // timeUntilTrigger, we spin here... don't worry // though, this spinning // doesn't even register 0.2% cpu usage on a pentium 4. numPauses = (int)(timeUntilTrigger / spinInterval); while (numPauses >= 0 && !_signaled) { try { Thread.Sleep(spinInterval); } catch (ThreadInterruptedException) { } now = DateTime.UtcNow; timeUntilTrigger = (long)(triggerTime - now).TotalMilliseconds; numPauses = (int)(timeUntilTrigger / spinInterval); } if (_signaled) { try { ReleaseAcquiredTrigger(trigger); } catch (Exception ex) { log.Error("ReleaseAcquiredTrigger: RuntimeException " + ex.Message, ex); } _signaled = false; continue; } // set trigger to 'executing' TriggerFiredBundle bundle = null; lock (_pauseLock) { if (!_halted) { try { bundle = TriggerFired(trigger); } catch (Exception ex) { log.Error(string.Format(CultureInfo.InvariantCulture, "RuntimeException while firing trigger {0}", trigger.Name), ex); } } // it's possible to get 'null' if the trigger was paused, // blocked, or other similar occurances that prevent it being // fired at this time... or if the scheduler was shutdown (halted) if (bundle == null) { try { ReleaseAcquiredTrigger(trigger); } catch (SchedulerException) { } continue; } _threadPool.QueueUserWorkItem(new WaitCallback(ProcessJob), bundle); } continue; } } else { // if(availTreadCount > 0) continue; // should never happen, if threadPool.blockForAvailableThreads() follows contract } // this looping may seem a bit silly, but it's the current // work-around // for a dead-lock that can occur if the Thread.sleep() is replaced // with // a obj.wait() that gets notified when the signal is set... // so to be able to detect the signal change without sleeping the // entier // getRandomizedIdleWaitTime(), we spin here... don't worry though, // the // CPU usage of this spinning can't even be measured on a pentium // 4. now = DateTime.UtcNow; DateTime waitTime = now.AddMilliseconds(GetRandomizedIdleWaitTime()); long timeUntilContinue = (long)(waitTime - now).TotalMilliseconds; spinInterval = 10; numPauses = (int)(timeUntilContinue / spinInterval); while (numPauses > 0 && !_signaled) { try { Thread.Sleep(10); } catch (ThreadInterruptedException) { } now = DateTime.UtcNow; timeUntilContinue = (long)(waitTime - now).TotalMilliseconds; numPauses = (int)(timeUntilContinue / spinInterval); } } catch (ThreadAbortException) { } catch (Exception ex) { log.Error("Runtime error occured in main trigger firing loop.", ex); } } }