SchedulerOperation.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. #if HAS_AWAIT
  5. using System.ComponentModel;
  6. using System.Runtime.CompilerServices;
  7. using System.Threading;
  8. namespace System.Reactive.Concurrency
  9. {
  10. /// <summary>
  11. /// Represents an awaitable scheduler operation. Awaiting the object causes the continuation to be posted back to the originating scheduler's work queue.
  12. /// </summary>
  13. public sealed class SchedulerOperation
  14. {
  15. private readonly Func<Action, IDisposable> _schedule;
  16. private readonly CancellationToken _cancellationToken;
  17. private readonly bool _postBackToOriginalContext;
  18. internal SchedulerOperation(Func<Action, IDisposable> schedule, CancellationToken cancellationToken)
  19. : this(schedule, cancellationToken, false)
  20. {
  21. }
  22. internal SchedulerOperation(Func<Action, IDisposable> schedule, CancellationToken cancellationToken, bool postBackToOriginalContext)
  23. {
  24. _schedule = schedule;
  25. _cancellationToken = cancellationToken;
  26. _postBackToOriginalContext = postBackToOriginalContext;
  27. }
  28. /// <summary>
  29. /// Controls whether the continuation is run on the originating synchronization context (false by default).
  30. /// </summary>
  31. /// <param name="continueOnCapturedContext">true to run the continuation on the captured synchronization context; false otherwise (default).</param>
  32. /// <returns>Scheduler operation object with configured await behavior.</returns>
  33. public SchedulerOperation ConfigureAwait(bool continueOnCapturedContext)
  34. {
  35. return new SchedulerOperation(_schedule, _cancellationToken, continueOnCapturedContext);
  36. }
  37. /// <summary>
  38. /// Gets an awaiter for the scheduler operation, used to post back the continuation.
  39. /// </summary>
  40. /// <returns>Awaiter for the scheduler operation.</returns>
  41. public SchedulerOperationAwaiter GetAwaiter()
  42. {
  43. return new SchedulerOperationAwaiter(_schedule, _cancellationToken, _postBackToOriginalContext);
  44. }
  45. }
  46. /// <summary>
  47. /// (Infrastructure) Scheduler operation awaiter type used by the code generated for C# await and Visual Basic Await expressions.
  48. /// </summary>
  49. [EditorBrowsable(EditorBrowsableState.Never)]
  50. public sealed class SchedulerOperationAwaiter
  51. : INotifyCompletion
  52. {
  53. private readonly Func<Action, IDisposable> _schedule;
  54. private readonly CancellationToken _cancellationToken;
  55. private readonly bool _postBackToOriginalContext;
  56. private readonly CancellationTokenRegistration _ctr;
  57. internal SchedulerOperationAwaiter(Func<Action, IDisposable> schedule, CancellationToken cancellationToken, bool postBackToOriginalContext)
  58. {
  59. _schedule = schedule;
  60. _cancellationToken = cancellationToken;
  61. _postBackToOriginalContext = postBackToOriginalContext;
  62. if (cancellationToken.CanBeCanceled)
  63. {
  64. _ctr = _cancellationToken.Register(Cancel);
  65. }
  66. }
  67. /// <summary>
  68. /// Indicates whether the scheduler operation has completed. Returns false unless cancellation was already requested.
  69. /// </summary>
  70. public bool IsCompleted
  71. {
  72. get { return _cancellationToken.IsCancellationRequested; }
  73. }
  74. /// <summary>
  75. /// Completes the scheduler operation, throwing an OperationCanceledException in case cancellation was requested.
  76. /// </summary>
  77. public void GetResult()
  78. {
  79. _cancellationToken.ThrowIfCancellationRequested();
  80. }
  81. /// <summary>
  82. /// Registers the continuation with the scheduler operation.
  83. /// </summary>
  84. /// <param name="continuation">Continuation to be run on the originating scheduler.</param>
  85. public void OnCompleted(Action continuation)
  86. {
  87. if (continuation == null)
  88. throw new ArgumentNullException(nameof(continuation));
  89. if (_continuation != null)
  90. throw new InvalidOperationException(Strings_Core.SCHEDULER_OPERATION_ALREADY_AWAITED);
  91. if (_postBackToOriginalContext)
  92. {
  93. var ctx = SynchronizationContext.Current;
  94. if (ctx != null)
  95. {
  96. var original = continuation;
  97. continuation = () =>
  98. {
  99. //
  100. // No need for OperationStarted and OperationCompleted calls here;
  101. // this code is invoked through await support and will have a way
  102. // to observe its start/complete behavior, either through returned
  103. // Task objects or the async method builder's interaction with the
  104. // SynchronizationContext object.
  105. //
  106. // In general though, Rx doesn't play nicely with synchronization
  107. // contexts objects at the scheduler level. It's possible to start
  108. // async operations by calling Schedule, without a way to observe
  109. // their completion. Not interacting with SynchronizationContext
  110. // is a concious design decision as the performance impact was non
  111. // negligable and our schedulers abstract over more constructs.
  112. //
  113. ctx.Post(a => ((Action)a)(), original);
  114. };
  115. }
  116. }
  117. var ran = 0;
  118. _continuation = () =>
  119. {
  120. if (Interlocked.Exchange(ref ran, 1) == 0)
  121. {
  122. _ctr.Dispose(); // no null-check needed (struct)
  123. continuation();
  124. }
  125. };
  126. _work = _schedule(_continuation);
  127. }
  128. private volatile Action _continuation;
  129. private volatile IDisposable _work;
  130. private void Cancel()
  131. {
  132. var w = _work;
  133. if (w != null)
  134. w.Dispose();
  135. var c = _continuation;
  136. if (c != null)
  137. c();
  138. }
  139. }
  140. }
  141. #endif