C# 如何确保IObservable只枚举/执行一次

C# 如何确保IObservable只枚举/执行一次,c#,system.reactive,C#,System.reactive,我正在编写一个ICommand,它执行一个异步操作,并将结果发布到一个可观察的序列中。结果应该是懒惰的——除非有人同意结果,否则什么都不会发生。如果用户对结果进行了订阅,则应该取消订阅。我下面的代码(非常简化)通常可以正常工作。棘手的是,当调用Execute时,我希望异步操作只发生一次,即使结果有许多订户。我想我只需要在发布结果之前做一个Replay.RefCount。但这不起作用。或者至少在我的测试中,当可观察函数快速完成时,它不起作用。第一个订阅服务器获取整个结果,包括完成消息,该消息导致释

我正在编写一个
ICommand
,它执行一个异步操作,并将结果发布到一个可观察的序列中。结果应该是懒惰的——除非有人同意结果,否则什么都不会发生。如果用户对结果进行了订阅,则应该取消订阅。我下面的代码(非常简化)通常可以正常工作。棘手的是,当调用Execute时,我希望异步操作只发生一次,即使结果有许多订户。我想我只需要在发布结果之前做一个
Replay.RefCount
。但这不起作用。或者至少在我的测试中,当可观察函数快速完成时,它不起作用。第一个订阅服务器获取整个结果,包括完成消息,该消息导致释放已发布的结果,然后为第二个订阅服务器完全重新创建。我使用的一个技巧是在执行函数的末尾插入一个1刻度延迟。这为第二个订阅者提供了足够的时间来获得结果

这黑客合法吗?我不确定它是如何工作的,也不确定它是否能在非测试场景中运行

有什么方法可以确保结果只被枚举一次?我认为可能有效的一件事是,当用户订阅结果时,我会将结果复制到
ReplaySubject
中并发布该结果。但我想不出如何让它工作。第一个订阅者应该开始计算结果并将结果填充到ReplaySubject中,而第二个订阅者应该只看到ReplaySubject。也许这是某种可以观察到的自定义
。创建

public class AsyncCommand<T> : IObservable<IObservable<T>>
{
    private readonly Func<IObservable<T>> _execute;
    Subject<IObservable<T>> _results;

    public AsyncCommand(Func<IObservable<T>> execute)
    {
        _execute = execute;
        _results = new Subject<IObservable<T>>();
    }

    // This would be ICommand.Execute, but I've simplified here
    public void Execute() => _results.OnNext(
        _execute()
        .Delay(TimeSpan.FromTicks(1)) // Take this line out and the test fails
        .Replay()
        .RefCount());

    // Subscribe to the inner observable to see the results of command execution
    public IDisposable Subscribe(IObserver<IObservable<T>> observer) =>
        _results.Subscribe(observer);
}

[TestClass]
public class AsyncCommandTest
{
    [TestMethod]
    public void IfSubscribeManyTimes_OnlyExecuteOnce()
    {
        int executionCount = 0;
        var cmd = new AsyncCommand<int>(() => Observable.Create<int>(obs =>
        {
            obs.OnNext(Interlocked.Increment(ref executionCount));
            obs.OnCompleted();
            return Disposable.Empty;
        }));
        cmd.Merge().Subscribe();
        cmd.Merge().Subscribe();
        cmd.Execute();
        Assert.AreEqual(1, executionCount);
    }
}
公共类AsyncCommand:IObservable
{
私有只读函数执行;
受试者-结果;
公共异步命令(Func execute)
{
_执行=执行;
_结果=新受试者();
}
//这将是ICommand.Execute,但我在这里简化了它
public void Execute()=>\u results.OnNext(
_执行()
.Delay(TimeSpan.FromTicks(1))//取出此行,测试失败
.Replay()
.RefCount());
//订阅内部可观察对象以查看命令执行的结果
公共IDisposable订阅(IObserver观察员)=>
_结果:订阅(观察员);
}
[测试类]
公共类异步命令测试
{
[测试方法]
如果订阅次数仅限执行次数()
{
int executionCount=0;
var cmd=new AsyncCommand(()=>Observable.Create(obs=>
{
obs.OnNext(联锁增量(参考执行计数));
obs.OnCompleted();
返回一次性。空;
}));
cmd.Merge().Subscribe();
cmd.Merge().Subscribe();
cmd.Execute();
Assert.AreEqual(1,executionCount);
}
}
下面是我如何尝试使用ReplaySubject的。它可以工作,但结果不会延迟发布,订阅也会丢失-将订阅处理为结果不会取消操作

public void Execute()
{
    ReplaySubject<T> result = new ReplaySubject<T>();
    var lostSubscription = _execute().Subscribe(result);
    _results.OnNext(result);
}
public void Execute()
{
ReplaySubject结果=新建ReplaySubject();
var lostSubscription=_execute().Subscribe(结果);
_results.OnNext(result);
}
这似乎有效

public void Execute()
{
    int subscriptionCount = 0;
    int executionCount = 0;
    var result = new ReplaySubject<T>();
    var disposeLastSubscription = new Subject<Unit>();
    _results.OnNext(Observable.Create<T>(obs =>
    {
        Interlocked.Increment(ref subscriptionCount);
        if (Interlocked.Increment(ref executionCount) == 1)
        {
            IDisposable copySourceToReplay = Observable
                .Defer(_execute)
                .TakeUntil(disposeLastSubscription)
                .Subscribe(result);
        }
        return new CompositeDisposable(
            result.Subscribe(obs),
            Disposable.Create(() =>
            {
                if (Interlocked.Decrement(ref subscriptionCount) == 0)
                {
                    disposeLastSubscription.OnNext(Unit.Default);
                }
            }));
    }));
}
public void Execute()
{
int subscriptionCount=0;
int executionCount=0;
var result=新的ReplaySubject();
var disposelastssubscription=新主题();
_results.OnNext(Observable.Create(obs=>
{
联锁。增量(参考subscriptionCount);
if(联锁增量(参考执行计数)==1)
{
IDisposable copySourceToReplay=可观察
.Defer(_execute)
.TakeUntil(处置订阅)
.订阅(结果);
}
返回新的CompositeDisposable(
结果。订阅(obs),
一次性。创建(()=>
{
if(联锁减量(参考下标计数)==0)
{
disposeLastSubscription.OnNext(单位默认值);
}
}));
}));
}