Fiber.cs
Go to the documentation of this file.
1 /*
2 
3 Author: Aaron Oneal, http://aarononeal.info
4 
5 Copyright (c) 2012 Spicy Pixel, http://spicypixel.com
6 
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 "Software"), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14 
15 The above copyright notice and this permission notice shall be
16 included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26 */
27 using System;
28 using System.Collections;
30 using System.Linq;
31 using System.Threading;
32 
33 namespace SpicyPixel.Threading
34 {
67  public partial class Fiber
68  {
72  [ThreadStatic]
73  private static Fiber currentFiber;
74 
75  private static FiberFactory fiberFactory = new FiberFactory ();
76 
80  private static int nextId = 0;
81 
82  private IEnumerator coroutine;
83 
84  private Action action;
85  private Action<object> actionObject;
86  private object objectState;
87 
88  private Func<FiberInstruction> func;
89  private Func<object, FiberInstruction> funcObject;
90 
91  private FiberScheduler scheduler;
92  private int status = (int)FiberStatus.Created;
93  // must be int to work with Interlocked on Mono
94  private IDictionary<string, object> properties;
95 
96  internal CancellationToken cancelToken;
97  internal Fiber antecedent;
98  internal Queue<FiberContinuation> continuations;
99 
106  public static Fiber CurrentFiber {
107  get {
108  return currentFiber;
109  }
110  private set {
111  currentFiber = value;
112  }
113  }
114 
119  public static FiberFactory Factory {
120  get {
121  return fiberFactory;
122  }
123  }
124 
125  static int CheckTimeout (TimeSpan timeout)
126  {
127  try {
128  return checked((int)timeout.TotalMilliseconds);
129  } catch (System.OverflowException) {
130  throw new ArgumentOutOfRangeException ("timeout");
131  }
132  }
133 
149  public IDictionary<string, object> Properties {
150  get {
151  if (properties == null)
152  properties = new Dictionary<string, object> ();
153 
154  return properties;
155  }
156  }
157 
164  internal FiberScheduler Scheduler {
165  get { return scheduler; }
166  set { scheduler = value; }
167  }
168 
175  public string Name { get; set; }
176 
181  public object ResultAsObject { get; internal set; }
182 
187  public bool IsCanceled {
188  get {
189  return (FiberStatus)status == FiberStatus.Canceled;
190  }
191  }
192 
203  public bool IsCompleted {
204  get {
205  return (FiberStatus)status >= FiberStatus.RanToCompletion;
206  }
207  }
208 
216  public bool IsFaulted {
217  get {
218  return (FiberStatus)status == FiberStatus.Faulted;
219  }
220  }
221 
226  public Exception Exception {
227  get;
228  private set;
229  }
230 
237  public FiberStatus Status {
238  get { return (FiberStatus)status; }
239  set { status = (int)value; }
240  }
241 
250  public Fiber Antecedent {
251  get { return antecedent; }
252  }
253 
267  get { return cancelToken; }
268  }
269 
273  private void FinishCompletion ()
274  {
275  if (continuations == null)
276  return;
277 
278  while (true) {
279  if (continuations.Count == 0)
280  return;
281 
282  var continuation = continuations.Dequeue ();
283  continuation.Execute ();
284  }
285  }
286 
291  internal void CancelContinuation ()
292  {
293  status = (int)FiberStatus.Canceled;
294  FinishCompletion ();
295  }
296 
302  internal void FaultContinuation (Exception exception)
303  {
304  status = (int)FiberStatus.Faulted;
305  Exception = exception;
306  FinishCompletion ();
307  }
308 
315  public int Id { get; private set; }
316 
323  public Fiber (IEnumerator coroutine) : this (coroutine, CancellationToken.None)
324  {
325  }
326 
334  public Fiber (IEnumerator coroutine, CancellationToken cancellationToken)
335  {
336  Id = nextId++;
337  this.coroutine = coroutine;
338  this.cancelToken = cancellationToken;
339  }
340 
347  public Fiber (Action action) : this (action, CancellationToken.None)
348  {
349  }
350 
358  public Fiber (Action action, CancellationToken cancellationToken)
359  {
360  Id = nextId++;
361  this.action = action;
362  this.cancelToken = cancellationToken;
363  }
364 
374  public Fiber (Action<object> action, object state) : this (action, state, CancellationToken.None)
375  {
376  }
377 
388  public Fiber (Action<object> action, object state, CancellationToken cancellationToken)
389  {
390  Id = nextId++;
391  this.actionObject = action;
392  this.objectState = state;
393  this.cancelToken = cancellationToken;
394  }
395 
402  public Fiber (Func<FiberInstruction> func) : this (func, CancellationToken.None)
403  {
404  }
405 
413  public Fiber (Func<FiberInstruction> func, CancellationToken cancellationToken)
414  {
415  Id = nextId++;
416  this.func = func;
417  this.cancelToken = cancellationToken;
418  }
419 
429  public Fiber (Func<object, FiberInstruction> func, object state) : this (func, state, CancellationToken.None)
430  {
431  }
432 
443  public Fiber (Func<object, FiberInstruction> func, object state, CancellationToken cancellationToken)
444  {
445  Id = nextId++;
446  this.funcObject = func;
447  this.objectState = state;
448  this.cancelToken = cancellationToken;
449  }
450 
454  public void Start ()
455  {
456  Start (FiberScheduler.Current);
457  }
458 
469  public void Start (FiberScheduler scheduler)
470  {
471  // It would be unusual to attempt to start a Fiber more than once,
472  // but to be safe and to support calling from any thread
473  // use Interlocked with boxing on the enum.
474 
475  var originalState = (FiberStatus)Interlocked.CompareExchange (ref status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created);
476  if (originalState != FiberStatus.Created) {
477  originalState = (FiberStatus)Interlocked.CompareExchange (ref status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.WaitingForActivation);
478  if (originalState != FiberStatus.WaitingForActivation) {
479  throw new InvalidOperationException ("A fiber cannot be started again once it has begun running or has completed.");
480  }
481  }
482 
483  this.scheduler = scheduler;
484  ((IFiberScheduler)this.scheduler).QueueFiber (this);
485  }
486 
490  public void ThrowIfCanceled ()
491  {
492  if (IsCanceled)
494  }
495 
499  public void ThrowIfFaulted ()
500  {
501  if (IsFaulted)
502  throw Exception;
503  }
504 
509  {
510  ThrowIfCanceled ();
511  ThrowIfFaulted ();
512  }
513 
520  internal FiberInstruction Execute ()
521  {
522  //Console.WriteLine ("Fiber {0}:{1} start executing", Id, Status);
523 
524  // Sanity check the scheduler. Since this is a scheduler
525  // only issue, this test happens first before execution
526  // and before allowing an exception handler to take over.
527  if (IsCompleted)
528  throw new InvalidOperationException ("An attempt was made to execute a completed Fiber. This indicates a logic error in the scheduler.");
529 
530  // Setup thread globals with this scheduler as the owner.
531  // The sync context is also setup when the scheduler changes.
532  // Setting the sync context isn't a simple assign in .NET
533  // so don't do this until needed.
534  //
535  // Doing this setup in Execute() frees derived schedulers from the
536  // burden of setting up the sync context or scheduler. It also allows the
537  // current scheduler to change on demand when there is more than one
538  // scheduler running per thread.
539  //
540  // For example, each MonoBehaviour in Unity is assigned its own
541  // scheduler since the behavior determines the lifetime of tasks.
542  // The active scheduler then can vary depending on which behavior is
543  // currently executing tasks. Unity will decide outside of this framework
544  // which behaviour to execute in which order and therefore which
545  // scheduler is active. The active scheduler in this case can
546  // only be determined at the time of fiber execution.
547  //
548  // Unlike CurrentFiber below, this does not need to be stacked
549  // once set because the scheduler will never change during fiber
550  // execution. Only something outside of the scheduler would
551  // change the scheduler.
552  if (FiberScheduler.Current != scheduler)
553  FiberScheduler.SetCurrentScheduler (scheduler, true);
554 
555  // Push the current fiber onto the stack and pop it in finally.
556  // This must be stacked because the fiber may spawn another
557  // fiber which the scheduler may choose to inline which would result
558  // in a new fiber temporarily taking its place as current.
559  var lastFiber = Fiber.CurrentFiber;
560  Fiber.CurrentFiber = this;
561 
562  // Start the fiber if not started
563  Interlocked.CompareExchange (ref status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created);
564  Interlocked.CompareExchange (ref status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun);
565 
566  try {
567  object result = null;
568 
569  // Execute the coroutine or action
570  if (coroutine != null) {
571  //Console.WriteLine ("Fiber {0}:{1} start executing coroutine", Id, Status);
572  // Execute coroutine
573  if (coroutine.MoveNext ()) {
574  // Get result of execution
575  result = coroutine.Current;
576 
577  // If the coroutine returned a stop directly
578  // the fiber still needs to process it
579  if (result is StopInstruction)
580  Stop (FiberStatus.RanToCompletion);
581  } else {
582  // Coroutine finished executing
583  result = Stop (FiberStatus.RanToCompletion);
584  }
585  //Console.WriteLine ("Fiber {0}:{1} end executing coroutine", Id, Status);
586  } else if (action != null) {
587  // Execute action
588  action ();
589 
590  // Action finished executing
591  result = Stop (FiberStatus.RanToCompletion);
592  } else if (actionObject != null) {
593  // Execute action
594  actionObject (objectState);
595 
596  // Action finished executing
597  result = Stop (FiberStatus.RanToCompletion);
598  } else if (func != null) {
599  result = func ();
600  func = null;
601 
602  if (result is StopInstruction)
603  Stop (FiberStatus.RanToCompletion);
604  } else if (funcObject != null) {
605  result = funcObject (objectState);
606  funcObject = null;
607 
608  if (result is StopInstruction)
609  Stop (FiberStatus.RanToCompletion);
610  } else {
611  // Func execution nulls out the function
612  // so the scheduler will return to here
613  // when complete and then stop.
614  result = Stop (FiberStatus.RanToCompletion);
615  }
616 
617  // Treat null as a special case
618  if (result == null)
619  return FiberInstruction.YieldToAnyFiber;
620 
621  // Return instructions or throw if invalid
622  var instruction = result as FiberInstruction;
623  if (instruction == null) {
624  // If the result was an enumerator there is a nested coroutine to execute
625  if (result is IEnumerator) {
626  instruction = new YieldUntilComplete (Fiber.Factory.StartNew (result as IEnumerator, cancelToken, scheduler));
627  } else if (result is Fiber) {
628  // Convert fibers into yield instructions
629  instruction = new YieldUntilComplete (result as Fiber);
630  } else {
631  // Pass through other values
632  return new ObjectInstruction (result);
633  }
634  }
635 
636  if (instruction is FiberResult) {
637  ResultAsObject = ((FiberResult)instruction).Result;
638  result = Stop (FiberStatus.RanToCompletion);
639  }
640 
641  // Verify same scheduler
642  if (instruction is YieldUntilComplete && ((YieldUntilComplete)instruction).Fiber.Scheduler != FiberScheduler.Current)
643  throw new InvalidOperationException ("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = "
644  + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString ())
645  + ", Fiber.Scheduler = " + (((YieldUntilComplete)instruction).Fiber.Scheduler == null ? "null" : ((YieldUntilComplete)instruction).Fiber.Scheduler.ToString ()));
646 
647  var yieldToFiberInstruction = instruction as YieldToFiber;
648  if (yieldToFiberInstruction != null) {
649  // Start fibers yielded to that aren't running yet
650  Interlocked.CompareExchange (ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.WaitingToRun, (int)FiberStatus.Created);
651  var originalState = (FiberStatus)Interlocked.CompareExchange (ref yieldToFiberInstruction.Fiber.status, (int)FiberStatus.Running, (int)FiberStatus.WaitingToRun);
652  if (originalState == FiberStatus.WaitingToRun)
653  yieldToFiberInstruction.Fiber.scheduler = scheduler;
654 
655  // Can't switch to completed fibers
656  if (yieldToFiberInstruction.Fiber.IsCompleted)
657  throw new InvalidOperationException ("An attempt was made to yield to a completed fiber.");
658 
659  // Verify scheduler
660  if (yieldToFiberInstruction.Fiber.Scheduler != FiberScheduler.Current)
661  throw new InvalidOperationException ("Currently only fibers belonging to the same scheduler may be yielded to. FiberScheduler.Current = "
662  + (FiberScheduler.Current == null ? "null" : FiberScheduler.Current.ToString ())
663  + ", Fiber.Scheduler = " + (yieldToFiberInstruction.Fiber.Scheduler == null ? "null" : yieldToFiberInstruction.Fiber.Scheduler.ToString ()));
664  }
665 
666  //Console.WriteLine ("Fiber {0}:{1} returning {2}", Id, Status, instruction);
667  return instruction;
668  } catch (System.Threading.OperationCanceledException cancelException) {
669  // Handle as proper cancellation only if the token matches.
670  // Otherwise treat it as a fault.
671  if (cancelException.CancellationToken == cancelToken) {
672  this.Exception = null;
673  return Stop (FiberStatus.Canceled);
674  } else {
675  this.Exception = cancelException;
676  return Stop (FiberStatus.Faulted);
677  }
678  } catch (Exception fiberException) {
679  this.Exception = fiberException;
680  return Stop (FiberStatus.Faulted);
681  } finally {
682  // Pop the current fiber
683  Fiber.CurrentFiber = lastFiber;
684 
685  //Console.WriteLine ("Fiber {0}:{1} end executing", Id, Status);
686  }
687  }
688 
689  private StopInstruction Stop (FiberStatus finalStatus)
690  {
691  // Interlocked isn't necessary because we don't need
692  // the initial value (stops are final). They are also
693  // only called by the scheduler thread during Execute().
694  status = (int)finalStatus;
695  FinishCompletion ();
696  return FiberInstruction.Stop;
697  }
698  }
699 }
Schedules fibers for execution.
Fiber(Action< object > action, object state, CancellationToken cancellationToken)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:388
void ThrowIfCanceled()
Throws if canceled.
Definition: Fiber.cs:490
void ThrowIfFaulted()
Throws if faulted.
Definition: Fiber.cs:499
Fiber(Action< object > action, object state)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:374
A Fiber is a lightweight means of scheduling work that enables multiple units of processing to execut...
Represents a fiber instruction to be processed by a FiberScheduler.
A Fiber Factory for creating fibers with the same options.
Definition: FiberFactory.cs:9
Fiber(Action action)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:347
void ThrowIfCanceledOrFaulted()
Throws if canceled or faulted.
Definition: Fiber.cs:508
Fiber(IEnumerator coroutine, CancellationToken cancellationToken)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:334
void Start()
Start executing the fiber using the default scheduler on the thread.
Definition: Fiber.cs:454
Fiber(Func< FiberInstruction > func)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:402
Interface used by Fiber to access protected methods of the scheduler.
static FiberScheduler Current
Gets the default fiber scheduler for the thread.
Fiber(Func< object, FiberInstruction > func, object state)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:429
Fiber(Func< object, FiberInstruction > func, object state, CancellationToken cancellationToken)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:443
Fiber(Func< FiberInstruction > func, CancellationToken cancellationToken)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:413
An instruction to terminate execution of the current fiber.
Fiber(IEnumerator coroutine)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:323
void Start(FiberScheduler scheduler)
Start executing the fiber using the specified scheduler.
Definition: Fiber.cs:469
Fiber(Action action, CancellationToken cancellationToken)
Initializes a new instance of the SpicyPixel.Threading.Fiber class.
Definition: Fiber.cs:358
FiberStatus
Represents the current state of a fiber.
Definition: FiberStatus.cs:34
When no continuation options are specified, default behavior should be used to execute a continuation...
static Fiber CurrentFiber
Gets the currently executing fiber on this thread.
Definition: Fiber.cs:106