所有观察者在Observable序列上完成后执行操作

时间:2017-02-16 15:30:50

标签: c# system.reactive observable

我有一些可观察的序列,例如:

var period = TimeSpan.FromSeconds(0.5);
var observable = Observable
    .Interval(period)
    .Publish()
    .RefCount();

我想在后台线程上对此序列的元素执行一些硬计算,并在完成所有计算时执行一些最终操作。所以我想要这样的东西:

observable.ObserveOn(Scheduler.Default).Subscribe(i => ComplexComputation1(i));
observable.ObserveOn(Scheduler.Default).Subscribe(i => ComplexComputation2(i));
// next observer must be called only after ComplexComputation1/2 complete on input i
observable.Subscribe(i => FinalAction(i));

我可以在Rx中执行此操作吗?或者这可能违反了反应式编程的一些原则,我应该在这种情况下使用另一种方法?

3 个答案:

答案 0 :(得分:2)

在反应模式中使用计算有序的序列是非常危险的。

您可以做的一件事是让每个复杂的计算在完成后发出一个事件。然后,您可以让消费者观察员在收到前面步骤完成的消息后执行计算。

另一种可能的解决方案是创建一个定期触发的具体序列块。这降低了解决方案的可并行性。

observable.ObserveOn(Scheduler.Default).Subscribe(i => 
{     
    ComplexComputation1(i));
    ComplexComputation2(i));
    FinalAction(i);
}

答案 1 :(得分:1)

为了测试这一点,我创建了以下方法来帮助说明事件的顺序:

public void ComplexComputation1(long i)
{
    Console.WriteLine("Begin ComplexComputation1");
    Thread.Sleep(100);
    Console.WriteLine("End ComplexComputation1");
}

public void ComplexComputation2(long i)
{
    Console.WriteLine("Begin ComplexComputation2");
    Thread.Sleep(100);
    Console.WriteLine("End ComplexComputation2");
}

public void FinalAction(long i)
{
    Console.WriteLine("Begin FinalAction");
    Thread.Sleep(100);
    Console.WriteLine("End FinalAction");
}

你的原始代码是这样的:

Begin FinalAction
Begin ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
End FinalAction
End ComplexComputation1
Begin FinalAction
Begin ComplexComputation1
Begin ComplexComputation2
End FinalAction
End ComplexComputation2
End ComplexComputation1
Begin FinalAction
Begin ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
End ComplexComputation1
End FinalAction
...

很容易强制代码在单个后台线程上按顺序运行。只需使用EventLoopScheduler

var els = new EventLoopScheduler();

observable.ObserveOn(els).Subscribe(i => ComplexComputation1(i));
observable.ObserveOn(els).Subscribe(i => ComplexComputation2(i));
// next observer must be called only after ComplexComputation1/2 complete on input i
observable.ObserveOn(els).Subscribe(i => FinalAction(i));

这给出了:

Begin ComplexComputation1
End ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
Begin FinalAction
End FinalAction
Begin ComplexComputation1
End ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
Begin FinalAction
End FinalAction
Begin ComplexComputation1
End ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
Begin FinalAction
End FinalAction

但是一旦你引入Scheduler.Default,这就行不通了。

或多或少的简单选项是:

var cc1s = observable.ObserveOn(Scheduler.Default).Select(i => { ComplexComputation1(i); return Unit.Default; });
var cc2s = observable.ObserveOn(Scheduler.Default).Select(i => { ComplexComputation2(i); return Unit.Default; });

observable.Zip(cc1s.Zip(cc2s, (cc1, cc2) => Unit.Default), (i, cc) => i).Subscribe(i => FinalAction(i));

按预期工作。

你得到一个很好的序列:

Begin ComplexComputation1
Begin ComplexComputation2
End ComplexComputation1
End ComplexComputation2
Begin FinalAction
End FinalAction
Begin ComplexComputation2
Begin ComplexComputation1
End ComplexComputation2
End ComplexComputation1
Begin FinalAction
End FinalAction
Begin ComplexComputation1
Begin ComplexComputation2
End ComplexComputation2
End ComplexComputation1
Begin FinalAction
End FinalAction

答案 2 :(得分:0)

这似乎是一个嵌套的observable组合的简单例子(SelectMany / Merge / Concat)和Zip

在这里,我冒昧地假设Long Running方法返回Task。 但是,如果他们不这样做,那么慢速阻塞同步方法可以用Observable.Start(()=>ComplexComputation1(x))包裹。

void Main()
{
    var period = TimeSpan.FromSeconds(0.5);
    var observable = Observable
        .Interval(period)
        .Publish()
        .RefCount();

    var a = observable.Select(i => ComplexComputation1(i).ToObservable())
                .Concat();
    var b = observable.Select(i => ComplexComputation2(i).ToObservable())
                .Concat();

    a.Zip(b, Tuple.Create)
        .Subscribe(pair => FinalAction(pair.Item1, pair.Item2));
}

// Define other methods and classes here
Random rnd = new Random();
private async Task<long> ComplexComputation1(long i)
{
    await Task.Delay(rnd.Next(50, 1000));
    return i;
}
private async Task<long> ComplexComputation2(long i)
{
    await Task.Delay(rnd.Next(50, 1000));
    return i;
}

private void FinalAction(long a, long b)
{

}