/// <summary> /// Executes the fiber until it ends or yields. /// </summary> /// <returns> /// A fiber instruction to be processed by the scheduler. /// </returns> internal FiberInstruction Execute() { // 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 (FiberState == FiberState.Stopped) { throw new InvalidOperationException("An attempt was made to execute a stopped 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; try { // Process an abort if pending if (FiberState == FiberState.AbortRequested) { throw new FiberAbortException(); } object result; // Execute the coroutine or action if (coroutine != null) { // Execute coroutine if (coroutine.MoveNext()) { // Get result of execution result = coroutine.Current; } else { // Coroutine finished executing result = Stop(); } } else if (action != null) { // Execute action action(); // Action finished executing result = Stop(); } else if (actionObject != null) { // Execute action actionObject(objectState); // Action finished executing result = Stop(); } else if (func != null) { result = func(); func = null; } else if (funcObject != null) { result = funcObject(objectState); funcObject = null; } else { // Func execution nulls out the function // so the scheduler will return to here // when complete and then stop. result = Stop(); } // 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) { return(new ObjectInstruction(result)); } else { // 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 var originalState = (FiberState)Interlocked.CompareExchange(ref yieldToFiberInstruction.Fiber.fiberState, (int)FiberState.Running, (int)FiberState.Unstarted); if (originalState == FiberState.Unstarted) { yieldToFiberInstruction.Fiber.scheduler = scheduler; } // Can't switch to stopped fibers if (yieldToFiberInstruction.Fiber.FiberState == FiberState.Stopped) { throw new InvalidOperationException("An attempt was made to yield to a stopped 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())); } } return(instruction); } } catch (Exception fiberException) { // If an exception occurs allow the handler to run. // Fiber execution will terminate even with a handler // because it's not possible to resume an action or a // coroutine that throws an exception. // // The only exception that could result in a contiunation // is FiberAbortException. Handlers may choose to deny // the abort by calling Fiber.ResetAbort(). try { // Invoke the exception handler (which could throw) var unhandledException = this.unhandledException; var eventArgs = new FiberUnhandledExceptionEventArgs(this, fiberException); if (unhandledException != null) { foreach (var subscriber in unhandledException.GetInvocationList()) { subscriber.DynamicInvoke(this, eventArgs); if (eventArgs.Handled) { break; } } } // See if an abort was requested. The flag is thread safe because // it is only ever touched by the scheduler thread. if (resetAbortRequested) { resetAbortRequested = false; fiberState = (int)FiberState.Running; return(FiberInstruction.YieldToAnyFiber); // signal the scheduler to continue } else { if (!eventArgs.Handled) { throw fiberException; } else { return(Stop()); } } } catch (Exception fiberOrHandlerException) { // The exception wasn't from an abort or it wasn't reset // and so the exception needs to happen. // Clear reset requests (if any). This is needed here in case // the exceptionHandler above threw after ResetAbort() was // called. // // The flag is thread safe because // it is only ever touched by the scheduler thread. resetAbortRequested = false; // Stop the fiber. This will invoke completion handlers // but they won't know this fiber failed due to an exception // unless they handled the exception above or wait for // the scheduler to do something after the throw below. Stop(); // Throw the exception back to the scheduler. throw fiberOrHandlerException; } } finally { // Pop the current fiber Fiber.CurrentFiber = lastFiber; } }
/// <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); } }