/// <summary> /// Dispatches an asynchronous message to a synchronization context (the <see cref="FiberScheduler"/>). /// </summary> /// <remarks> /// The scheduler may choose to inline the callback if the Post is /// executed from the scheduler thread. /// </remarks> /// <param name='d'> /// Callback to invoke /// </param> /// <param name='state'> /// State to pass /// </param> public override void Post(SendOrPostCallback d, object state) { // The scheduler may choose to inline this if the Post // is executed from the scheduler thread. Fiber.StartNew(() => { d(state); }, scheduler); }
/// <summary> /// Dispatches an synchronous message to a synchronization context (the <see cref="FiberScheduler"/>). /// </summary> /// <remarks> /// The callback is always inlined if Send is executed from the /// scheduler thread regardless of any scheduler specific inline settings. /// Because inlining always occurs when on the scheduler thread, the /// caller must manage stack depth. /// </remarks> /// <param name='d'> /// Callback to invoke /// </param> /// <param name='state'> /// State to pass /// </param> public override void Send(SendOrPostCallback d, object state) { // Force inlining if the threads match. if (scheduler.SchedulerThread == Thread.CurrentThread) { d(state); return; } // FIXME: This could block indefinitely if the scheduler goes down // before executing the task. Need another wait handle here or // better approach. Maybe add a WaitHandle to the fiber itself or // add Join(). // The threads don't match, so queue the action // and wait for it to complete. ManualResetEvent wait = new ManualResetEvent(false); Fiber.StartNew(() => { d(state); wait.Set(); }, scheduler); wait.WaitOne(); }
/// <summary> /// Run the blocking scheduler loop and perform the specified number of updates per second. /// </summary> /// <remarks> /// Not all schedulers support a blocking run loop that can be invoked by the caller. /// The system scheduler is designed so that a custom run loop could be implemented /// by a derived type. Everything used to execute Run() is available to a derived scheduler. /// </remarks> /// <param name='fiber'> /// The optional fiber to start execution from. If this is <c>null</c>, the loop /// will continue to execute until cancelled. Otherwise, the loop will terminate /// when the fiber terminates. /// </param> /// <param name='updatesPerSecond'> /// Updates to all fibers per second. A value of <c>0</c> (the default) will execute fibers /// any time they are ready to do work instead of waiting to execute on a specific frequency. /// </param> /// <param name='token'> /// A cancellation token that can be used to stop execution. /// </param> public override void Run(Fiber fiber, CancellationToken token, float updatesPerSecond) { long frequencyTicks = (long)(updatesPerSecond * (float)TimeSpan.TicksPerSecond); // min time between updates (duration) long startTicks = 0; // start of update time (marker) long endTicks = 0; // end of update time (marker) long sleepTicks; // time to sleep (duration) long wakeTicks; // ticks before wake (duration) int sleepMilliseconds; // ms to sleep (duration) int wakeMilliseconds; // ms before wake (duration) float wakeMarkerInSeconds; // time of wake in seconds (marker) var mainFiberCompleteCancelSource = new CancellationTokenSource(); if (isDisposed) { throw new ObjectDisposedException(GetType().FullName); } // Run is not re-entrant, make sure we're not running if (!runWaitHandle.WaitOne(0)) { throw new InvalidOperationException("Run is already executing and is not re-entrant"); } // Verify arguments if (updatesPerSecond < 0f) { throw new ArgumentOutOfRangeException("updatesPerSecond", "The updatesPerSecond must be >= 0"); } // Get a base time for better precision long baseTicks = DateTime.Now.Ticks; // Build wait list to terminate execution var waitHandleList = new List <WaitHandle>(4); waitHandleList.Add(schedulerEventWaitHandle); waitHandleList.Add(disposeWaitHandle); if (token.CanBeCanceled) { waitHandleList.Add(token.WaitHandle); } try { if (fiber != null) { // Add the main fiber to the wait list so when it completes // the wait handle falls through. waitHandleList.Add(mainFiberCompleteCancelSource.Token.WaitHandle); // Start the main fiber if it isn't running yet YieldUntilComplete waitOnFiber; if (fiber.FiberState == FiberState.Unstarted) { waitOnFiber = fiber.Start(this); } else { waitOnFiber = new YieldUntilComplete(fiber); } // Start another fiber that waits on the main fiber to complete. // When it does, it raises a cancellation. Fiber.StartNew(CancelWhenComplete(waitOnFiber, mainFiberCompleteCancelSource), this); } WaitHandle[] waitHandles = waitHandleList.ToArray(); waitHandleList.Remove(schedulerEventWaitHandle); WaitHandle[] sleepWaitHandles = waitHandleList.ToArray(); runWaitHandle.Reset(); while (true) { // Stop executing if cancelled if ((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0)) { return; } // Snap current time startTicks = DateTime.Now.Ticks; // Update using this time marker (and convert ticks to s) Update((float)((double)(startTicks - baseTicks) / (double)TimeSpan.TicksPerSecond)); // Only sleep to next frequency cycle if one was specified if (updatesPerSecond > 0f) { // Snap end time endTicks = DateTime.Now.Ticks; // Sleep at least until next update sleepTicks = frequencyTicks - (endTicks - startTicks); if (sleepTicks > 0) { sleepMilliseconds = (int)(sleepTicks / TimeSpan.TicksPerMillisecond); WaitHandle.WaitAny(sleepWaitHandles, sleepMilliseconds); // Stop executing if cancelled if ((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0)) { return; } } } // Now keep sleeping until it's time to update while (ExecutingFiberCount == 0) { // Assume we wait forever (e.g. until a signal) wakeMilliseconds = -1; // If there are sleeping fibers, then set a wake time if (GetNextFiberWakeTime(out wakeMarkerInSeconds)) { wakeTicks = baseTicks; wakeTicks += (long)((double)wakeMarkerInSeconds * (double)TimeSpan.TicksPerSecond); wakeTicks -= DateTime.Now.Ticks; // If there was a waiting fiber and it's already past time to awake then stop waiting if (wakeTicks <= 0) { break; } wakeMilliseconds = (int)(wakeTicks / TimeSpan.TicksPerMillisecond); } // There was no waiting fiber and we will wait for another signal, // or there was a waiting fiber and we wait until that time. WaitHandle.WaitAny(waitHandles, wakeMilliseconds); // Stop executing if cancelled if ((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0)) { return; } } } } finally { // Clear queues Fiber deqeueFiber; while (executingFibers.TryDequeue(out deqeueFiber)) { ; } Tuple <Fiber, float> dequeueSleepingFiber; while (sleepingFibers.TryDequeue(out dequeueSleepingFiber)) { ; } // Reset time currentTime = 0f; // Set for dispose runWaitHandle.Set(); } }