private IEnumerator CancelWhenComplete(YieldUntilComplete waitOnFiber, CancellationTokenSource cancelSource) { yield return waitOnFiber; cancelSource.Cancel(); }
private void OnFiberInstruction(Fiber fiber, FiberInstruction instruction, out bool fiberQueued, out Fiber nextFiber) { fiberQueued = false; nextFiber = null; YieldUntilComplete yieldUntilComplete = instruction as YieldUntilComplete; if (yieldUntilComplete != null) { // The additional complexity below is because this was going // to handle waiting for completions for fibers from other threads. // Currently fibers must belong to the same thread and this is enforced // by the instructions themselves for now. int completeOnce = 0; // FIXME: If we support multiple schedulers in the future // this callback could occur from another thread and // therefore after Dispose(). Would probably need a lock. // Watch for completion EventHandler <EventArgs> completed; completed = (sender, e) => { var originalCompleteOnce = Interlocked.CompareExchange(ref completeOnce, 1, 0); if (originalCompleteOnce != 0) { return; } yieldUntilComplete.Fiber.Completed -= completed; //QueueFiberForExecution(fiber); QueueFiber(fiber); // optionally execute inline when the completion occurs }; yieldUntilComplete.Fiber.Completed += completed; // If the watched fiber is already complete then continue immediately if (yieldUntilComplete.Fiber.FiberState == FiberState.Stopped) { completed(yieldUntilComplete.Fiber, EventArgs.Empty); } fiberQueued = true; return; } YieldForSeconds yieldForSeconds = instruction as YieldForSeconds; if (yieldForSeconds != null) { QueueFiberForSleep(fiber, currentTime + yieldForSeconds.Seconds); fiberQueued = true; return; } YieldToFiber yieldToFiber = instruction as YieldToFiber; if (yieldToFiber != null) { RemoveFiberFromQueues(yieldToFiber.Fiber); nextFiber = yieldToFiber.Fiber; fiberQueued = false; return; } }
/// <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(); } }
/// <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(); } }
private IEnumerator CancelWhenComplete(YieldUntilComplete waitOnFiber, CancellationTokenSource cancelSource) { yield return(waitOnFiber); cancelSource.Cancel(); }
private void OnFiberInstruction(Fiber fiber, FiberInstruction instruction, out bool fiberQueued, out Fiber nextFiber) { fiberQueued = false; nextFiber = null; YieldUntilComplete yieldUntilComplete = instruction as YieldUntilComplete; if (yieldUntilComplete != null) { // The additional complexity below is because this was going // to handle waiting for completions for fibers from other threads. // Currently fibers must belong to the same thread and this is enforced // by the instructions themselves for now. int completeOnce = 0; // FIXME: If we support multiple schedulers in the future // this callback could occur from another thread and // therefore after Dispose(). Would probably need a lock. yieldUntilComplete.Fiber.ContinueWith((f) => { var originalCompleteOnce = Interlocked.CompareExchange(ref completeOnce, 1, 0); if (originalCompleteOnce != 0) { return; } QueueFiber(fiber); // optionally execute inline when the completion occurs // If f.Status != RanToCompletion then this fiber needs to transition to the same state // or faults won't propegate //if (f.Status != FiberStatus.RanToCompletion) { // if (f.IsCanceled) { // if (f.CancellationToken == fiber.CancellationToken) { // fiber.CancelContinuation (); // } else { // fiber.FaultContinuation (new System.Threading.OperationCanceledException ()); // } // } else if (f.IsFaulted) { // fiber.FaultContinuation (f.Exception); // } // RemoveFiberFromQueues (fiber); //} else { // QueueFiber (fiber); // optionally execute inline when the completion occurs //} }); fiberQueued = true; return; } YieldForSeconds yieldForSeconds = instruction as YieldForSeconds; if (yieldForSeconds != null) { QueueFiberForSleep(fiber, currentTime + yieldForSeconds.Seconds); fiberQueued = true; return; } YieldToFiber yieldToFiber = instruction as YieldToFiber; if (yieldToFiber != null) { RemoveFiberFromQueues(yieldToFiber.Fiber); nextFiber = yieldToFiber.Fiber; fiberQueued = false; return; } }
/// <summary> /// Executes the fiber until it ends or yields. /// </summary> /// <returns> /// A fiber instruction to be processed by the scheduler. /// </returns> internal FiberInstruction Execute() { //Console.WriteLine ("Fiber {0}:{1} start executing", Id, Status); // Sanity check the scheduler. Since this is a scheduler // only issue, this test happens first before execution // and before allowing an exception handler to take over. if (IsCompleted) throw new InvalidOperationException ("An attempt was made to execute a completed Fiber. This indicates a logic error in the scheduler."); // Setup thread globals with this scheduler as the owner. // The sync context is also setup when the scheduler changes. // Setting the sync context isn't a simple assign in .NET // so don't do this until needed. // // Doing this setup in Execute() frees derived schedulers from the // burden of setting up the sync context or scheduler. It also allows the // current scheduler to change on demand when there is more than one // scheduler running per thread. // // For example, each MonoBehaviour in Unity is assigned its own // scheduler since the behavior determines the lifetime of tasks. // The active scheduler then can vary depending on which behavior is // currently executing tasks. Unity will decide outside of this framework // which behaviour to execute in which order and therefore which // scheduler is active. The active scheduler in this case can // only be determined at the time of fiber execution. // // Unlike CurrentFiber below, this does not need to be stacked // once set because the scheduler will never change during fiber // execution. Only something outside of the scheduler would // change the scheduler. if (FiberScheduler.Current != scheduler) FiberScheduler.SetCurrentScheduler (scheduler, true); // Push the current fiber onto the stack and pop it in finally. // This must be stacked because the fiber may spawn another // fiber which the scheduler may choose to inline which would result // in a new fiber temporarily taking its place as current. var lastFiber = Fiber.CurrentFiber; Fiber.CurrentFiber = this; // Start the fiber if not started Interlocked.CompareExchange (ref status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created); Interlocked.CompareExchange (ref status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun); try { object result = null; // Execute the coroutine or action if (coroutine != null) { //Console.WriteLine ("Fiber {0}:{1} start executing coroutine", Id, Status); // Execute coroutine if (coroutine.MoveNext ()) { // Get result of execution result = coroutine.Current; // If the coroutine returned a stop directly // the fiber still needs to process it if (result is StopInstruction) Stop (FiberStatus.RanToCompletion); } else { // Coroutine finished executing result = Stop (FiberStatus.RanToCompletion); } //Console.WriteLine ("Fiber {0}:{1} end executing coroutine", Id, Status); } else if (action != null) { // Execute action action (); // Action finished executing result = Stop (FiberStatus.RanToCompletion); } else if (actionObject != null) { // Execute action actionObject (objectState); // Action finished executing result = Stop (FiberStatus.RanToCompletion); } else if (func != null) { result = func (); func = null; if (result is StopInstruction) Stop (FiberStatus.RanToCompletion); } else if (funcObject != null) { result = funcObject (objectState); funcObject = null; if (result is StopInstruction) Stop (FiberStatus.RanToCompletion); } else { // Func execution nulls out the function // so the scheduler will return to here // when complete and then stop. result = Stop (FiberStatus.RanToCompletion); } // Treat null as a special case if (result == null) return FiberInstruction.YieldToAnyFiber; // Return instructions or throw if invalid var instruction = result as FiberInstruction; if (instruction == null) { // If the result was an enumerator there is a nested coroutine to execute if (result is IEnumerator) { instruction = new YieldUntilComplete (Fiber.Factory.StartNew (result as IEnumerator, cancelToken, scheduler)); } else if (result is Fiber) { // Convert fibers into yield instructions instruction = new YieldUntilComplete (result as Fiber); } else { // Pass through other values return new ObjectInstruction (result); } } if (instruction is FiberResult) { ResultAsObject = ((FiberResult)instruction).Result; result = Stop (FiberStatus.RanToCompletion); } // Verify same scheduler if (instruction is YieldUntilComplete && ((YieldUntilComplete)instruction).Fiber.Scheduler != FiberScheduler.Current) throw new InvalidOperationException ("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = " + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString ()) + ", Fiber.Scheduler = " + (((YieldUntilComplete)instruction).Fiber.Scheduler == null ? "null" : ((YieldUntilComplete)instruction).Fiber.Scheduler.ToString ())); var yieldToFiberInstruction = instruction as YieldToFiber; if (yieldToFiberInstruction != null) { // Start fibers yielded to that aren't running yet Interlocked.CompareExchange (ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created); var originalState = (FiberStatus)Interlocked.CompareExchange (ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun); if (originalState == FiberStatus.WaitingToRun) yieldToFiberInstruction.Fiber.scheduler = scheduler; // Can't switch to completed fibers if (yieldToFiberInstruction.Fiber.IsCompleted) throw new InvalidOperationException ("An attempt was made to yield to a completed fiber."); // Verify scheduler if (yieldToFiberInstruction.Fiber.Scheduler != FiberScheduler.Current) throw new InvalidOperationException ("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = " + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString ()) + ", Fiber.Scheduler = " + (yieldToFiberInstruction.Fiber.Scheduler == null ? "null" : yieldToFiberInstruction.Fiber.Scheduler.ToString ())); } //Console.WriteLine ("Fiber {0}:{1} returning {2}", Id, Status, instruction); return instruction; } catch (System.Threading.OperationCanceledException cancelException) { // Handle as proper cancellation only if the token matches. // Otherwise treat it as a fault. if (cancelException.CancellationToken == cancelToken) { this.Exception = null; return Stop (FiberStatus.Canceled); } else { this.Exception = cancelException; return Stop (FiberStatus.Faulted); } } catch (Exception fiberException) { this.Exception = fiberException; return Stop (FiberStatus.Faulted); } finally { // Pop the current fiber Fiber.CurrentFiber = lastFiber; //Console.WriteLine ("Fiber {0}:{1} end executing", Id, Status); } }
/// <summary> /// Executes the fiber until it ends or yields. /// </summary> /// <returns> /// A fiber instruction to be processed by the scheduler. /// </returns> internal FiberInstruction Execute() { //Console.WriteLine ("Fiber {0}:{1} start executing", Id, Status); // Sanity check the scheduler. Since this is a scheduler // only issue, this test happens first before execution // and before allowing an exception handler to take over. if (IsCompleted) { throw new InvalidOperationException("An attempt was made to execute a completed Fiber. This indicates a logic error in the scheduler."); } // Setup thread globals with this scheduler as the owner. // The sync context is also setup when the scheduler changes. // Setting the sync context isn't a simple assign in .NET // so don't do this until needed. // // Doing this setup in Execute() frees derived schedulers from the // burden of setting up the sync context or scheduler. It also allows the // current scheduler to change on demand when there is more than one // scheduler running per thread. // // For example, each MonoBehaviour in Unity is assigned its own // scheduler since the behavior determines the lifetime of tasks. // The active scheduler then can vary depending on which behavior is // currently executing tasks. Unity will decide outside of this framework // which behaviour to execute in which order and therefore which // scheduler is active. The active scheduler in this case can // only be determined at the time of fiber execution. // // Unlike CurrentFiber below, this does not need to be stacked // once set because the scheduler will never change during fiber // execution. Only something outside of the scheduler would // change the scheduler. if (FiberScheduler.Current != scheduler) { FiberScheduler.SetCurrentScheduler(scheduler, true); } // Push the current fiber onto the stack and pop it in finally. // This must be stacked because the fiber may spawn another // fiber which the scheduler may choose to inline which would result // in a new fiber temporarily taking its place as current. var lastFiber = Fiber.CurrentFiber; Fiber.CurrentFiber = this; // Start the fiber if not started Interlocked.CompareExchange(ref status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created); Interlocked.CompareExchange(ref status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun); try { object result = null; // Execute the coroutine or action if (coroutine != null) { //Console.WriteLine ("Fiber {0}:{1} start executing coroutine", Id, Status); // Execute coroutine if (coroutine.MoveNext()) { // Get result of execution result = coroutine.Current; // If the coroutine returned a stop directly // the fiber still needs to process it if (result is StopInstruction) { Stop(FiberStatus.RanToCompletion); } } else { // Coroutine finished executing result = Stop(FiberStatus.RanToCompletion); } //Console.WriteLine ("Fiber {0}:{1} end executing coroutine", Id, Status); } else if (action != null) { // Execute action action(); // Action finished executing result = Stop(FiberStatus.RanToCompletion); } else if (actionObject != null) { // Execute action actionObject(objectState); // Action finished executing result = Stop(FiberStatus.RanToCompletion); } else if (func != null) { result = func(); func = null; if (result is StopInstruction) { Stop(FiberStatus.RanToCompletion); } } else if (funcObject != null) { result = funcObject(objectState); funcObject = null; if (result is StopInstruction) { Stop(FiberStatus.RanToCompletion); } } else { // Func execution nulls out the function // so the scheduler will return to here // when complete and then stop. result = Stop(FiberStatus.RanToCompletion); } // Treat null as a special case if (result == null) { return(FiberInstruction.YieldToAnyFiber); } // Return instructions or throw if invalid var instruction = result as FiberInstruction; if (instruction == null) { // If the result was an enumerator there is a nested coroutine to execute if (result is IEnumerator) { instruction = new YieldUntilComplete(Fiber.Factory.StartNew(result as IEnumerator, cancelToken, scheduler)); } else if (result is Fiber) { // Convert fibers into yield instructions instruction = new YieldUntilComplete(result as Fiber); } else { // Pass through other values return(new ObjectInstruction(result)); } } if (instruction is FiberResult) { ResultAsObject = ((FiberResult)instruction).Result; result = Stop(FiberStatus.RanToCompletion); } // Verify same scheduler if (instruction is YieldUntilComplete && ((YieldUntilComplete)instruction).Fiber.Scheduler != FiberScheduler.Current) { throw new InvalidOperationException("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = " + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString()) + ", Fiber.Scheduler = " + (((YieldUntilComplete)instruction).Fiber.Scheduler == null ? "null" : ((YieldUntilComplete)instruction).Fiber.Scheduler.ToString())); } var yieldToFiberInstruction = instruction as YieldToFiber; if (yieldToFiberInstruction != null) { // Start fibers yielded to that aren't running yet Interlocked.CompareExchange(ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created); var originalState = (FiberStatus)Interlocked.CompareExchange(ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun); if (originalState == FiberStatus.WaitingToRun) { yieldToFiberInstruction.Fiber.scheduler = scheduler; } // Can't switch to completed fibers if (yieldToFiberInstruction.Fiber.IsCompleted) { throw new InvalidOperationException("An attempt was made to yield to a completed fiber."); } // Verify scheduler if (yieldToFiberInstruction.Fiber.Scheduler != FiberScheduler.Current) { throw new InvalidOperationException("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = " + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString()) + ", Fiber.Scheduler = " + (yieldToFiberInstruction.Fiber.Scheduler == null ? "null" : yieldToFiberInstruction.Fiber.Scheduler.ToString())); } } //Console.WriteLine ("Fiber {0}:{1} returning {2}", Id, Status, instruction); return(instruction); } catch (System.Threading.OperationCanceledException cancelException) { // Handle as proper cancellation only if the token matches. // Otherwise treat it as a fault. if (cancelException.CancellationToken == cancelToken) { this.Exception = null; return(Stop(FiberStatus.Canceled)); } else { this.Exception = cancelException; return(Stop(FiberStatus.Faulted)); } } catch (Exception fiberException) { this.Exception = fiberException; return(Stop(FiberStatus.Faulted)); } finally { // Pop the current fiber Fiber.CurrentFiber = lastFiber; //Console.WriteLine ("Fiber {0}:{1} end executing", Id, Status); } }