/// <summary> /// Pause scheduler until all scheduled work items on the given context have completed. /// </summary> /// <param name="context">The scheduler context on which to wait.</param> internal void PauseForQuiescence(SchedulerContext context) { if (context.Empty.WaitOne(0) || !this.started || this.completed) { return; } // Pulse the future queue to process any work items that are either due or // non-reachable due to having a start time past the finalization time. this.futureQueuePulse.Set(); if (!this.isSchedulerThread.Value && !this.allowSchedulingOnExternalThreads) { // this is not a scheduler thread, so just wait for the context to empty and return context.Empty.WaitOne(); return; } // continue executing work items on this scheduler thread while waiting for the context to empty while (!this.forcedShutdownRequested && !context.Empty.WaitOne(0)) { WorkItem wi; if (this.nextWorkitem.Value != null) { // if there is a queued local work item, try to run it first wi = (WorkItem)this.nextWorkitem.Value; this.nextWorkitem.Value = null; this.counters?.Decrement(SchedulerCounters.LocalQueueCount); if (wi.SyncLock.TryLock()) { this.counters?.Increment(SchedulerCounters.LocalWorkitemsPerSecond); goto ExecuteWorkItem; } else { // it's locked, so let someone else have it this.Schedule(wi, false); this.counters?.Increment(SchedulerCounters.LocalToGlobalPromotions); } } // try to get another item from the global queue if (!this.globalWorkitems.TryDequeue(out wi)) { // no work left continue; } this.counters?.Increment(SchedulerCounters.GlobalWorkitemsPerSecond); ExecuteWorkItem: this.currentWorkitemTime.Value = wi.StartTime; this.ExecuteAndRelease(wi.SyncLock, wi.Callback, wi.SchedulerContext); } }
private void ExecuteAndRelease(SynchronizationLock synchronizationObject, Action action, SchedulerContext context) { try { action(); this.counters?.Increment(SchedulerCounters.WorkitemsPerSecond); } catch (Exception e) when(this.errorHandler(e)) { } finally { synchronizationObject.Release(); context.Exit(); } }
/// <summary> /// Stops scheduling further work items on the given context. /// </summary> /// <param name="context">The context on which scheduling should cease.</param> public void StopScheduling(SchedulerContext context) { // wait for already scheduled work to finish this.PauseForQuiescence(context); context.Stop(); }
/// <summary> /// Starts scheduling work on the given context. /// </summary> /// <param name="context">The context on which scheduling should start.</param> public void StartScheduling(SchedulerContext context) { context.Start(this.Clock); }
/// <summary> /// Enqueues a workitem and, if possible, kicks off a worker thread to pick it up. /// </summary> /// <param name="synchronizationObject">Synchronization object.</param> /// <param name="action">Action to execute.</param> /// <param name="startTime">Scheduled start time.</param> /// <param name="context">The scheduler context on which to schedule the action.</param> /// <param name="asContinuation">Flag whether to execute once current operation completes.</param> /// <param name="allowSchedulingPastFinalization">Allow scheduling past finalization time.</param> public void Schedule(SynchronizationLock synchronizationObject, Action action, DateTime startTime, SchedulerContext context, bool asContinuation = true, bool allowSchedulingPastFinalization = false) { if (synchronizationObject == null || action == null || context == null) { throw new ArgumentNullException(); } if (!allowSchedulingPastFinalization && startTime > context.FinalizeTime) { return; } // Enter the context to track this new work item. The context will be exited // only after the work item has been successfully executed (or dropped). context.Enter(); this.Schedule(new WorkItem() { SyncLock = synchronizationObject, Callback = action, StartTime = startTime, SchedulerContext = context }, asContinuation); }
/// <summary> /// Attempts to execute the delegate immediately, on the calling thread, without scheduling. /// </summary> /// <param name="synchronizationObject">Synchronization object.</param> /// <param name="action">Action to execute.</param> /// <param name="startTime">Scheduled start time.</param> /// <param name="context">The scheduler context on which to execute the action.</param> /// <returns>Success flag.</returns> public bool TryExecute(SynchronizationLock synchronizationObject, Action action, DateTime startTime, SchedulerContext context) { if (this.forcedShutdownRequested || startTime > context.FinalizeTime) { return(true); } if (startTime > this.clock.GetCurrentTime() && this.delayFutureWorkitemsUntilDue) { return(false); } if (!this.isSchedulerThread.Value && !this.allowSchedulingOnExternalThreads) { // this thread is not ours, so return return(false); } // try to acquire a lock // however, if this thread already has the lock, we have to give up to keep the no-reentrancy guarantee if (!synchronizationObject.TryLock()) { return(false); } // ExecuteAndRelease assumes that the context has already been entered, so we must explicitly // enter it here. The context will be exited just prior to returning from ExecuteAndRelease. context.Enter(); this.ExecuteAndRelease(synchronizationObject, action, context); this.counters?.Increment(SchedulerCounters.ImmediateWorkitemsPerSecond); return(true); }
/// <summary> /// Attempts to execute the delegate immediately, on the calling thread, without scheduling. /// </summary> /// <typeparam name="T">Action argument type.</typeparam> /// <param name="synchronizationObject">Synchronization object.</param> /// <param name="action">Action to execute.</param> /// <param name="argument">Action argument.</param> /// <param name="startTime">Scheduled start time.</param> /// <param name="context">The scheduler context on which to execute the action.</param> /// <param name="outputActionTiming">Indicates whether to output action timing information.</param> /// <param name="actionStartTime">The time the runtime started executing the action.</param> /// <param name="actionEndTime">The time the runtime ended executing the action.</param> /// <returns>Success flag.</returns> public bool TryExecute <T>( SynchronizationLock synchronizationObject, Action <T> action, T argument, DateTime startTime, SchedulerContext context, bool outputActionTiming, out DateTime actionStartTime, out DateTime actionEndTime) { actionStartTime = DateTime.MinValue; actionEndTime = DateTime.MinValue; if (this.forcedShutdownRequested || startTime > context.FinalizeTime) { actionStartTime = actionEndTime = this.clock.GetCurrentTime(); return(true); } if (startTime > this.clock.GetCurrentTime() && this.delayFutureWorkitemsUntilDue) { return(false); } if (!this.isSchedulerThread.Value && !this.allowSchedulingOnExternalThreads) { // this thread is not ours, so return return(false); } // try to acquire a lock on the sync context // however, if this thread already has the lock, we have to give up to keep the no-reentrancy guarantee if (!synchronizationObject.TryLock()) { return(false); } try { // Unlike ExecuteAndRelease, which assumes that the context has already been entered (e.g. // when the work item was first created), we need to explicitly enter the context prior to // running the action. The context will be exited in the finally clause. context.Enter(); this.counters?.Increment(SchedulerCounters.WorkitemsPerSecond); this.counters?.Increment(SchedulerCounters.ImmediateWorkitemsPerSecond); if (outputActionTiming) { actionStartTime = this.clock.GetCurrentTime(); } action(argument); } catch (Exception e) when(this.errorHandler(e)) { } finally { if (outputActionTiming) { actionEndTime = this.clock.GetCurrentTime(); } synchronizationObject.Release(); context.Exit(); } return(true); }