簡體   English   中英

在 Rx.NET 中,如何使 Subject 類似於 TaskCompletionSource 行為?

[英]In Rx.NET, how do I make a Subject to resemble TaskCompletionSource behavior?

在 Rx.NET 中,如何使Subject類似於TaskCompletionSource.Task行為?

即使完成,它也需要緩存和回復第一個事件。 AsyncSubjectReplaySubject(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

我可以使用TaskCompletionSourceTaskObservableExtensions.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中刪除,但目前WriteOnceBlockAsObservable的可組合性並不完美。

您可以用兩個主題來編寫它,一個重播主題用於發出已設置的值,另一個用於控制初始化。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM