Yield execution until the watched fiber on the same scheduler is complete.
Inheritance: FiberInstruction
 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;
            }
        }
Ejemplo n.º 7
0
        /// <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);
            }
        }
Ejemplo n.º 8
0
        /// <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);
            }
        }