141 lines
4.5 KiB
C#
141 lines
4.5 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT License.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Threading;
|
|
|
|
namespace System.Reactive.Linq.ObservableImpl
|
|
{
|
|
internal sealed class Next<TSource> : PushToPullAdapter<TSource, TSource>
|
|
{
|
|
public Next(IObservable<TSource> source)
|
|
: base(source)
|
|
{
|
|
}
|
|
|
|
protected override PushToPullSink<TSource, TSource> Run() => new _();
|
|
|
|
private sealed class _ : PushToPullSink<TSource, TSource>
|
|
{
|
|
private readonly object _gate;
|
|
private readonly SemaphoreSlim _semaphore;
|
|
|
|
public _()
|
|
{
|
|
_gate = new object();
|
|
_semaphore = new SemaphoreSlim(0, 1);
|
|
}
|
|
|
|
private bool _waiting;
|
|
private NotificationKind _kind;
|
|
private TSource? _value;
|
|
private Exception? _error;
|
|
|
|
public override void OnNext(TSource value)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
if (_waiting)
|
|
{
|
|
_value = value;
|
|
_kind = NotificationKind.OnNext;
|
|
_semaphore.Release();
|
|
}
|
|
|
|
_waiting = false;
|
|
}
|
|
}
|
|
|
|
public override void OnError(Exception error)
|
|
{
|
|
Dispose();
|
|
|
|
lock (_gate)
|
|
{
|
|
//
|
|
// BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
|
|
//
|
|
_error = error;
|
|
_kind = NotificationKind.OnError;
|
|
|
|
if (_waiting)
|
|
{
|
|
_semaphore.Release();
|
|
}
|
|
|
|
_waiting = false;
|
|
}
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
Dispose();
|
|
|
|
lock (_gate)
|
|
{
|
|
//
|
|
// BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
|
|
//
|
|
_kind = NotificationKind.OnCompleted;
|
|
|
|
if (_waiting)
|
|
{
|
|
_semaphore.Release();
|
|
}
|
|
|
|
_waiting = false;
|
|
}
|
|
}
|
|
|
|
public override bool TryMoveNext([MaybeNullWhen(false)] out TSource current)
|
|
{
|
|
var done = false;
|
|
|
|
lock (_gate)
|
|
{
|
|
_waiting = true;
|
|
|
|
//
|
|
// BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end.
|
|
//
|
|
done = _kind != NotificationKind.OnNext;
|
|
}
|
|
|
|
if (!done)
|
|
{
|
|
_semaphore.Wait();
|
|
}
|
|
|
|
//
|
|
// When we reach this point, we released the lock and got the next notification
|
|
// from the observer. We assume no concurrent calls to the TryMoveNext method
|
|
// are made (per general guidance on usage of IEnumerable<T>). If the observer
|
|
// enters the lock again, it should have quit it first, causing _waiting to be
|
|
// set to false, hence future accesses of the lock won't set the _kind, _value,
|
|
// and _error fields, until TryMoveNext is entered again and _waiting is reset
|
|
// to true. In conclusion, the fields are stable for read below.
|
|
//
|
|
// Notice we rely on memory barrier acquire/release behavior due to the use of
|
|
// the semaphore, not the lock (we're still under the lock when we release the
|
|
// semaphore in the On* methods!).
|
|
//
|
|
switch (_kind)
|
|
{
|
|
case NotificationKind.OnNext:
|
|
current = _value!;
|
|
return true;
|
|
case NotificationKind.OnError:
|
|
_error!.Throw();
|
|
break;
|
|
case NotificationKind.OnCompleted:
|
|
break;
|
|
}
|
|
|
|
current = default;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|