Next.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Threading;
  6. namespace System.Reactive.Linq.ObservableImpl
  7. {
  8. internal sealed class Next<TSource> : PushToPullAdapter<TSource, TSource>
  9. {
  10. public Next(IObservable<TSource> source)
  11. : base(source)
  12. {
  13. }
  14. protected override PushToPullSink<TSource, TSource> Run() => new _();
  15. private sealed class _ : PushToPullSink<TSource, TSource>
  16. {
  17. private readonly object _gate;
  18. private readonly SemaphoreSlim _semaphore;
  19. public _()
  20. {
  21. _gate = new object();
  22. _semaphore = new SemaphoreSlim(0, 1);
  23. }
  24. private bool _waiting;
  25. private NotificationKind _kind;
  26. private TSource? _value;
  27. private Exception? _error;
  28. public override void OnNext(TSource value)
  29. {
  30. lock (_gate)
  31. {
  32. if (_waiting)
  33. {
  34. _value = value;
  35. _kind = NotificationKind.OnNext;
  36. _semaphore.Release();
  37. }
  38. _waiting = false;
  39. }
  40. }
  41. public override void OnError(Exception error)
  42. {
  43. Dispose();
  44. lock (_gate)
  45. {
  46. //
  47. // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
  48. //
  49. _error = error;
  50. _kind = NotificationKind.OnError;
  51. if (_waiting)
  52. {
  53. _semaphore.Release();
  54. }
  55. _waiting = false;
  56. }
  57. }
  58. public override void OnCompleted()
  59. {
  60. Dispose();
  61. lock (_gate)
  62. {
  63. //
  64. // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
  65. //
  66. _kind = NotificationKind.OnCompleted;
  67. if (_waiting)
  68. {
  69. _semaphore.Release();
  70. }
  71. _waiting = false;
  72. }
  73. }
  74. public override bool TryMoveNext([MaybeNullWhen(false)] out TSource current)
  75. {
  76. var done = false;
  77. lock (_gate)
  78. {
  79. _waiting = true;
  80. //
  81. // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
  82. //
  83. done = _kind != NotificationKind.OnNext;
  84. }
  85. if (!done)
  86. {
  87. _semaphore.Wait();
  88. }
  89. //
  90. // When we reach this point, we released the lock and got the next notification
  91. // from the observer. We assume no concurrent calls to the TryMoveNext method
  92. // are made (per general guidance on usage of IEnumerable<T>). If the observer
  93. // enters the lock again, it should have quit it first, causing _waiting to be
  94. // set to false, hence future accesses of the lock won't set the _kind, _value,
  95. // and _error fields, until TryMoveNext is entered again and _waiting is reset
  96. // to true. In conclusion, the fields are stable for read below.
  97. //
  98. // Notice we rely on memory barrier acquire/release behavior due to the use of
  99. // the semaphore, not the lock (we're still under the lock when we release the
  100. // semaphore in the On* methods!).
  101. //
  102. switch (_kind)
  103. {
  104. case NotificationKind.OnNext:
  105. current = _value!;
  106. return true;
  107. case NotificationKind.OnError:
  108. _error!.Throw();
  109. break;
  110. case NotificationKind.OnCompleted:
  111. break;
  112. }
  113. current = default;
  114. return false;
  115. }
  116. }
  117. }
  118. }