我正在尝试创建一个具有以下特征的可观察管道:
对于我的生活,我无法弄清楚正确的语义来实现这一目标。我认为做这样的事情很简单:
Observable
.Defer(() => Observable
.Start(() => { /* do something */ })
.PublishLast()
.ConnectUntilCompleted());
ConnectUntilCompleted
只是听起来像这样:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> @this)
{
@this.Connect();
return @this;
}
当observable成功终止时,这似乎有效,但是当出现错误时则不行。任何订阅者都不会收到错误:
[Fact]
public void test()
{
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); })
.PublishLast()
.ConnectUntilCompleted());
// this does not throw!
o.Subscribe();
}
谁能告诉我我做错了什么?为什么Publish
不重播它收到的任何错误?
更新:它变得更加陌生:
[Fact]
public void test()
{
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); })
.PublishLast()
.ConnectUntilCompleted())
.Do(
_ => { },
ex => { /* this executes */ });
// this does not throw!
o.Subscribe();
o.Subscribe(
_ => { },
ex => { /* even though this executes */ });
}
答案 0 :(得分:3)
试试这个版本的ConnectUntilCompleted
方法:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> @this)
{
return Observable.Create<T>(o =>
{
var subscription = @this.Subscribe(o);
var connection = @this.Connect();
return new CompositeDisposable(subscription, connection);
});
}
允许Rx正常运行。
现在我已添加它以帮助显示正在发生的事情:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> @this)
{
return Observable.Create<T>(o =>
{
var disposed = Disposable.Create(() => Console.WriteLine("Disposed!"));
var subscription = Observable
.Defer<T>(() => { Console.WriteLine("Subscribing!"); return @this; })
.Subscribe(o);
Console.WriteLine("Connecting!");
var connection = @this.Connect();
return new CompositeDisposable(disposed, subscription, connection);
});
}
现在您的观察结果如下:
var o =
Observable
.Defer(() =>
Observable
.Start(() =>
{
Console.WriteLine("Started.");
throw new InvalidOperationException();
}))
.PublishLast()
.ConnectUntilCompleted();
最后一个关键是实际处理订阅中的错误 - 因此仅仅执行o.Subscribe()
是不够的。
这样做:
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
当我跑步时,我得到了这个:
Subscribing! Connecting! Subscribing! Connecting! Subscribing! Connecting! Started. Operation is not valid due to the current state of the object. Disposed! Operation is not valid due to the current state of the object. Disposed! Operation is not valid due to the current state of the object. Disposed!
请注意,“已启动”仅出现一次,但会报告错误三次。
(有时Started
在第一次订阅后出现在列表中较高位置。)
我认为这是你想要的描述。
答案 1 :(得分:2)
为了支持@Engimativity的回答,我想展示你应该如何运行你的测试,以便你不再得到这些&#34;惊喜&#34;。您的测试是非确定性的,因为它们是多线程/并发的。在未提供Observable.Start
的情况下使用IScheduler
会产生问题。如果您使用TestScheduler
运行测试,那么您的测试现在将是单线程且确定性的
[Test]
public void Test()
{
var testScheduler = new TestScheduler();
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); }, testScheduler)
.PublishLast()
.ConnectUntilCompleted());
var observer = testScheduler.CreateObserver<Unit>();
o.Subscribe(observer);
testScheduler.Start();
CollectionAssert.IsNotEmpty(observer.Messages);
Assert.AreEqual(NotificationKind.OnError, observer.Messages[0].Value.Kind);
}
答案 2 :(得分:0)
实现您的要求的另一种方法可能是:
var lazy = new Lazy<Task>(async () => { /* execute once */ }, isThreadSafe: true);
var o = Observable.FromAsync(() => lazy.Value);
第一次订阅时,lazy
将创建(并执行)任务。对于其他订阅,lazy
将返回相同(可能已经完成或失败)的任务。