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(单位默认值);
}
}));
}));
}