[英]In Rx.NET, how do I make a Subject to resemble TaskCompletionSource behavior?
在 Rx.NET 中,如何使Subject
類似於TaskCompletionSource.Task
行為?
即使完成,它也需要緩存和回復第一個事件。 AsyncSubject
和ReplaySubject(bufferSize: 1)
都不會這樣做。
例如(我們稱之為PromiseSubject
):
//var subj = new ReplaySubject<int>(bufferSize: 1);
var subj = new PromiseSubject<int>();
subj.Subscribe(i => Console.WriteLine(i));
subj.OnNext(1);
subj.OnNext(2);
subj.OnNext(3);
subj.OnCompleted();
subj.Subscribe(i => Console.WriteLine(i));
Console.ReadLine();
預期 output:
1
1
我可以使用TaskCompletionSource
、 TaskObservableExtensions.ToObservable
和自定義SubjectBase
派生的主題實現來完成它,但是有沒有使用 Rx 運算符組合的優雅方法呢?
更新,我最初通過TaskCompletionSource
嘗試:
public class PromiseSubject<T> : ISubject<T>
{
private readonly TaskCompletionSource<(bool HasValue, T Value)> _tcs;
private readonly IObservable<T> _observable;
public PromiseSubject()
{
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
_observable = _tcs.Task.ToObservable()
.Where(r => r.HasValue).Select(r => r.Value!);
}
public void OnCompleted() =>
_tcs.TrySetResult((false, default!));
public void OnError(Exception error) =>
_tcs.TrySetException(error);
public void OnNext(T value) =>
_tcs.TrySetResult((true, value));
public IDisposable Subscribe(IObserver<T> observer) =>
_observable.Subscribe(observer);
}
您所描述的內容聽起來與 TPL 數據流庫中的WriteOnceBlock<T>
非常相似。 數據流塊有一個方便的擴展方法AsObservable
,所以基於這個想法,一個實現看起來像這樣:
public class WriteOnceSubject<T> : ISubject<T>
{
private readonly WriteOnceBlock<T> _block = new WriteOnceBlock<T>(x => x);
public void OnCompleted() => _block.Complete();
public void OnError(Exception error) => ((ISourceBlock<T>)_block).Fault(error);
public void OnNext(T value) => _block.Post(value);
public IDisposable Subscribe(IObserver<T> observer)
=> _block.AsObservable().Subscribe(observer);
}
不幸的是,這個想法行不通。 WriteOnceSubject<T>
的訂閱者只會收到OnCompleted()
通知。 不會發出OnNext()
。 我剛剛在 GitHub 上發布了關於此問題的錯誤報告。
更新:這是微軟對錯誤報告的反饋,由 Stephen Toub 撰寫:
WriteOnceBlock
只有一個值,可以多次使用,因此一旦給定一個值,塊就會完成。AsObservable
檢查源是否已完成,並將其視為不會有更多數據到來的指示。 因此,如果您在將數據傳遞給WriteOnceBlock
之前訂閱了觀察者,WriteOnceBlock
將盡職盡責地在完成之前將該數據傳播到鏈接的目標,並且觀察者將收到它,但如果觀察者在WriteOnceBlock
完成后訂閱,它將假設沒有數據到來,它本身就會發出完成的信號。如果源代碼已經完成,這些檢查可能會從
AsObservable
中刪除,但目前WriteOnceBlock
與AsObservable
的可組合性並不完美。
您可以用兩個主題來編寫它,一個重播主題用於發出已設置的值,另一個用於控制初始化。
public class PromiseSubject<T> : ISubject<T>
{
private readonly Subject<T> initialize = new();
private readonly ReplaySubject<T> subject = new(1);
public PromiseSubject() => initialize.Subscribe(subject);
public void OnCompleted() => initialize.OnCompleted();
public void OnError(Exception error) => initialize.OnError(error);
public void OnNext(T value)
{
initialize.OnNext(value);
initialize.OnCompleted();
}
public IDisposable Subscribe(IObserver<T> observer) => subject.Subscribe(observer);
}
這是 Jeff Mercado 的回答的簡化版本。 我認為可以通過在第一個OnNext
之后完成ReplaySubject(bufferSize: 1)
來實現理想的行為。
實際上,正如@noseratio 在評論中指出的那樣, AsyncSubject<T>
更簡單,而且效率也更高,因為它將單個值存儲在字段而不是數組中。
public class WriteOnceSubject<T> : ISubject<T>
{
private readonly AsyncSubject<T> subject = new();
public void OnNext(T value) { subject.OnNext(value); subject.OnCompleted(); }
public void OnError(Exception error) => subject.OnError(error);
public void OnCompleted() => subject.OnCompleted();
public IDisposable Subscribe(IObserver<T> observer) => subject.Subscribe(observer);
}
所以在這一系列事件中:
writeOnceSubject.OnNext(1);
writeOnceSubject.OnNext(2);
writeOnceSubject.OnNext(3);
writeOnceSubject.OnCompleted();
writeOnceSubject.OnError(new Exception());
...除了第一個命令之外的所有命令都是無操作的。 稍后訂閱writeOnceSubject
時,它將發出存儲在其緩沖區中的值1
,然后是OnCompleted
通知。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.