在创建时将未知数量的可观察对象组合/合并在一起

时间:2014-10-13 08:35:34

标签: c# .net system.reactive

我想做的是:

  • 调用一个函数(DoWork)作为其工作的一部分,将通过多个Worker类订阅多个热门输入
  • 在调用该函数之前,订阅DoWork订阅的所有更新
  • 完成后,处理所有订阅
  • DoWork完成之前,至少有一个传入的事件可能会被触发。

问题:

  • Subject这是正确的方法吗?感觉应该有更好的方式吗?
  • 如何确保在处置Main中的订阅后,所有incomingX订阅也会被处理 - 即Main应该控制所有订阅的生命周期。

    void Main()
    {
        var worker = new Worker();
        using (worker.UpdateEvents.Subscribe(x => Console.WriteLine()))
        {
            worker.DoWork();
        }
    }
    
    public class Worker1
    {
        private readonly Subject<string> updateEvents = new Subject<string>();
    
        public IObservable<string> UpdateEvents { get { return updateEvents; } }
    
        public void DoWork()
        {
            // Do some work
            // subscribe to a hot observable (events coming in over the network)
            incoming1.Subscribe(updateEvents);
    
            var worker2 = new Worker2();
            worker2.UpdateEvents.Subscribe(updateEvents);
            worker2.DoWork();
        }
    }
    
    public class Worker2
    {
        private readonly Subject<string> updateEvents = new Subject<string>();
    
        public IObservable<string> UpdateEvents { get { return updateEvents; } }
    
        public void DoWork()
        {
            // Do some work
            // subscribe to some more events
            incoming2.Subscribe(updateEvents);
    
            var workerN = new WorkerN();
            workerN.UpdateEvents.Subscribe(updateEvents);
            workerN.DoWork();
        }
    }
    

2 个答案:

答案 0 :(得分:3)

詹姆斯的回答(使用SubjectMerge)捕捉了问题的本质。这个答案提供了一种我认为在这种情况下有用的模式(基于你对詹姆斯答案的评论)。

基本上,模式是让您的工作人员在调用IObservable之前公开调用者将订阅的DoWork。但是这种API(在调用B之前调用A)是有问题的,因为它引入了时间耦合。

为了消除时间耦合,你最终将你的工作者本身变成一个冷的Observable ,当调用者订阅时隐式调用DoWork。一旦你意识到冷可观察力的强大功能以及在观察者订阅时使用Observable.Create采取行动的能力,天空是你可以创建的Rx链的限制,而不需要达到Subject。以下是基于原始代码的示例。

Worker很简单。它只是订阅了incoming1Worker2Worker2稍微复杂一些。它订阅incoming2,执行一些额外的工作,然后最终订阅WorkerN

始终保持原始代码示例无法执行的正确OnErrorOnCompleted逻辑。这意味着Main看到的可观察流不会Complete直到所有的传入流和工作流完成。但是,只要任何传入的流或工作流失败,Main就会失败。只要有Subscribe(someSubject)个流完成,您的代码示例会多次调用Subject,导致incoming完成(因此Main的传入流完成)。

public class Worker1
{
    public IObservable<string> UpdateEvents { get; private set; };

    public Worker1()
    {
        // Each time someone subscribes, create a new worker2 and subscribe to the hot events as well as whatever worker2 produces.
        UpdateEvents = Observable.Create(observer =>
        {
            var worker2 = new Worker2();
            return incoming1.Merge(worker2.UpdateEvents).Subscribe(observer);
        });
    }
}

public class Worker2
{
    public IObservable<string> UpdateEvents { get; private set; };

    public Worker2()
    {
        // Each time someone subscribes, create a new worker and subscribe to the hot events as well as whatever worker2 produces.
        UpdateEvents = Observable.Create(observer =>
        {
            // maybe this version needs to do some stuff after it has subscribed to incoming2 but before it subscribes to workerN:
            var doWorkThenSubscribeToWorker = Observable.Create(o =>
            {
                DoWork(o);
                var worker = new WorkerN();
                return worker.UpdateEvents.Subscribe(o);
            }

            return incoming2.Merge(doWorkThenSubscribeToWorker).Subscribe(observer);
        });
    }

    private void DoWork(IObserver<string> observer)
    {
        // do some work
        observer.OnNext("result of work");
    }
}


void Main()
{
    var worker = new Worker();
    worker.UpdateEvents.Do(x => Console.WriteLine()).Wait();
}

答案 1 :(得分:1)

很难完全按照您的要求进行操作 - 我认为small but complete program会有所帮助。

也就是说,使用Subject将输入引入Rx管道没有任何问题 - 在StackOverflow和其他地方有很多关于它的文章,所以我赢了重新开始吧。

仅仅按照你问题的标题,我想知道以下是否适合你的目的?

组合动态数量的流

为此,您可以在流媒体流上使用Merge。您的流必须都是相同类型 - 如果不是,您可以创建合适的容器类型并使用Select将它们投影到该类型中。为简单起见,我假设统一类型为long

开始为流创建容器:

var container = new Subject<IObservable<long>>();

然后合并包含的流:

var combined = container.Merge();

订阅合并以通常方式使用结果,并将订阅部署为一次取消订阅所有流。

然后您可以按照以下方式添加流:

// assume we got this from somewhere - e.g. a "worker" factory function
// Observable.Create may well be helpful to create an observable
// that initiates getting data from a network connection upon its subscription
IObservable<long> someNewStream;

// add the someNewStream to the container (it will be subscribed to once added)
container.OnNext(someNewStream);

使用示例

// dump out the combined results to the console,
// IRL you would subscribe to this to process the results
var subscription = combined.Subscribe(Console.WriteLine);

// add a stream of longs
container.OnNext(Observable.Interval(TimeSpan.FromSeconds(1)));    
Console.WriteLine("Stream 1 added");
Console.ReadLine();

// add another stream
container.OnNext(Observable.Interval(TimeSpan.FromSeconds(1)));    
Console.WriteLine("Step 2");
Console.ReadLine();

// this will unsubscribe from all the live streams
subscription.Dispose();