.NET的IObserver <T>是否打算订阅多个IObservable?


9

.NET中也有IObservableIObserver接口(也在此处此处)。有趣的是,IObserver的具体实现并没有直接引用IObservable。它不知道订阅了谁。它只能调用取消订阅者。“请拉销以退订。”

编辑:取消订阅者实现IDisposable。我认为,采用这种方案是为了防止监听程序失效

但是,有两件事对我来说并不完全清楚。

  1. 内部的Unsubscriber类是否提供“订阅并忘记”行为?谁(确切地说是何时)致电IDisposable.Dispose()取消订阅者?垃圾收集器(GC)不确定。
    [免责声明:总体而言,与C#相比,我在C和C ++上花费的时间更多。]
  2. 如果我要向观察者K订阅可观察的L1并且观察者已经订阅了其他一些可观察的L2,应该怎么办?

    K.Subscribe(L1);
    K.Subscribe(L2);
    K.Unsubscribe();
    L1.PublishObservation(1003);
    L2.PublishObservation(1004);
    

    当我针对MSDN的示例运行此测试代码时,观察者仍然订阅L1。这在实际开发中将是特殊的。可能有3种方法可以改善此问题:

    • 如果观察者已经有一个取消订阅者实例(即它已经订阅了),那么在订阅一个新的提供者之前,它会悄悄地取消订阅原始提供者。这种方法掩盖了一个事实,即它不再订阅原始提供程序,这以后可能会感到惊讶。
    • 如果观察者已经具有取消订阅者实例,则将引发异常。行为规范的调用代码必须显式取消订阅观察者。
    • 观察者订阅了多个提供者。这是最吸引人的选项,但是可以使用IObservable和IObserver来实现吗?让我们来看看。观察者可以保留一个非订户对象列表:每个源一个。不幸的是,IObserver.OnComplete()没有提供回发给提供者的参考。因此,具有多个提供程序的IObserver实现将无法确定要取消订阅的提供程序。
  3. .NET的IObserver是否打算订阅多个IObservable?
    教科书对观察者模式的定义是否要求一个观察者必须能够订阅多个提供者?还是可选的并且依赖于实现?

Answers:


5

这两个接口实际上是Reactive Extensions(简称Rx)的一部分,无论何时要使用它们,都应该使用该库。

从技术上说,这些接口在mscrolib中,而不在任何Rx程序集中。我认为这是为了简化互操作性:通过这种方式,TPL Dataflow之类的库可以提供使用这些接口的成员,而无需实际引用Rx。

如果您使用Rx Subject作为的实现IObservableSubscribe则将返回IDisposable可用于退订的:

var observable = new Subject<int>();

var unsubscriber =
    observable.Subscribe(Observer.Create<int>(i => Console.WriteLine("1: {0}", i)));
observable.Subscribe(Observer.Create<int>(i => Console.WriteLine("2: {0}", i)));

unsubscriber.Dispose();

observable.OnNext(1003);
observable.OnNext(1004);

5

只是为了清除正式Rx设计指南中详细记录的内容,以及我网站IntroToRx.com上详尽的内容

  • 您不依靠GC来清理您的订阅。这里详细介绍
  • 没有Unsubscribe办法。您订阅了一个可观察的序列,并获得了订阅。然后,您可以处置该预订,表明您不再希望调用回调。
  • 可观察的序列不能完成一次以上(请参见Rx设计指南的第4节)。
  • 有许多消耗多个可观察序列的方法。在Reactivex.io以及IntroToRx 上也有大量有关此方面的信息。

具体来说,直接回答原始问题,您的用法就从头到尾。您不会将许多可观察的序列推入单个观察器中。您将可观察序列组合为单个可观察序列。然后,您订阅该单个序列。

代替

K.Subscribe(L1);
K.Subscribe(L2);
K.Unsubscribe();
L1.PublishObservation(1003);
L2.PublishObservation(1004);

这只是伪代码,在Rx的.NET实现中不起作用,您应该执行以下操作:

var source1 = new Subject<int>(); //was L1
var source2 = new Subject<int>(); //was L2

var subscription = source1
    .Merge(source2)
    .Subscribe(value=>Console.WriteLine("OnNext({0})", value));


source1.OnNext(1003);
source2.OnNext(1004);

subscription.Dispose();

现在,这并不完全适合最初的问题,但是我不知道K.Unsubscribe()应该怎么做(要取消订阅所有订阅,最后一个还是第一个订阅?!)


我可以简单地将订阅对象包含在“使用”块中吗?
罗伯特·奥斯赫勒

1
在这种同步情况下,您可以,但是Rx应该是异步的。在异步情况下,通常不能使用该using块。为订阅声明成本应该是几乎为零,所以你会Neter的using块,订阅,离开使用块(因此取消)使得代码相当无意义
李·坎贝尔

3

你是对的。该示例不适用于多个IObservable。

我猜OnComplete()没有提供参考,因为他们不希望IObservable必须保留它。如果我在写这篇文章,我可能会通过让Subscribe接受一个标识符作为第二个参数来支持多个订阅,该标识符将被传递回OnComplete()调用。所以你可以说

K.Subscribe(L1,"L1")
K.Subscribe(L2,"L2")
K.Unsubscribe("L1")

就目前而言,.NET IObserver似乎不适合多个观察者使用。但是我想您的主要对象(示例中的LocationReporter)可以具有

public Dictionary<String,IObserver> Observers;

那将使您能够支持

K.Subscribe(L1,"L1")
K.Subscribe(L2,"L2")
K.Unsubscribe("L1")

也一样

我想微软可能会争辩说,因此它们不需要直接在接口中支持多个IObservable。


我还认为,可观察的实现可以包含一个观察者列表。我也已经注意到,这IObserver.OnComplete()并不能识别呼叫的来源。如果观察者订阅了多个可观察的对象,那么它将不知道要取消订阅的对象。滑稽的。我想知道,.NET是否为观察者模式提供了更好的接口?
尼克·阿列克谢耶夫

如果要引用某个内容,则应实际使用引用而不是字符串。
2014年

这个答案帮助我解决了一个真实的错误。我当时Observable.Create()用来构建可观察对象,并使用将多个源可观察对象链接到其中Subscribe()。我无意间在一个代码路径中传递了一个完整的可观察对象。即使其他来源不完整,这也完成了我新创建的可观察对象。我花了年龄制定出什么我需要做的-开关Observable.Empty()Observable.Never()
Olly

0

我知道这是迟到了,但...

该接口我Observable<T>IObserver<T>没有的Rx ......他们是核心类型的一部分......但的Rx大量使用它们。

您可以自由地拥有任意数量(或更少)的观察者。如果您预期有多个观察者,则OnNext()对于每个观察到的事件,将呼叫路由到适当的观察者是可观察的责任。可观察对象可能需要您建议的列表或字典。

在某些情况下,只允许一个-在许多情况下都允许。例如,在CQRS / ES实现中,您可以在命令总线上为每种命令类型强制执行一个命令处理程序,同时可以在事件存储中为给定事件类型通知多个读取侧转换。

如其他答案所述,没有Unsubscribe。当您Subscribe通常做脏活时,请处理掉您得到的东西。观察者或其代理负责保存令牌,直到它不再希望接收进一步的通知为止。(问题1)

因此,在您的示例中:

K.Subscribe(L1);
K.Subscribe(L2);
K.Unsubscribe();
L1.PublishObservation(1003);
L2.PublishObservation(1004);

...更像是:

using ( var l1Token = K.Subscribe( L1 ) )
{
  using ( var l2Token = K.Subscribe( L2 );
  {
    L1.PublishObservation( 1003 );
    L2.PublishObservation( 1004 );
  } //--> effectively unsubscribing to L2 here

  L2.PublishObservation( 1005 );
}

...其中K会听到1003和1004,但不会听到1005。

在我看来,这仍然很有趣,因为从名义上讲,订阅是长期存在的事情……通常在计划执行期间。在这方面,它们与正常的.Net事件没有什么不同。

在许多示例Dispose中,令牌的确可以将观察者从可观察者的观察者列表中删除。我更喜欢令牌不具备太多的知识...因此,我已将我的订阅令牌概括为只调用传入的lambda(具有在订阅时捕获的标识信息:

public class SubscriptionToken<T>: IDisposable
{
  private readonly Action unsubscribe;

  private SubscriptionToken( ) { }
  public SubscriptionToken( Action unsubscribe )
  {
    this.unsubscribe = unsubscribe;
  }

  public void Dispose( )
  {
    unsubscribe( );
  }
}

...而可观察者可以在订阅期间安装取消订阅行为:

IDisposable Subscribe<T>( IObserver<T> observer )
{
  var subscriberId = Guid.NewGuid( );
  subscribers.Add( subscriberId, observer );

  return new SubscriptionToken<T>
  (
    ( ) =>
    subscribers.Remove( subscriberId );
  );
}

如果观察者正在从多个可观察对象中捕获事件,则可能需要确保事件本身中存在某种关联信息...就像.Net事件与一样sender。这是否重要取决于您。正如您已正确推理的那样,它没有放入。(问题3)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.