如何管理依赖观察者的可观察订阅?

时间:2014-08-21 20:13:35

标签: c# system.reactive reactive-programming

此示例控制台应用程序有2个可观察对象。第一个将数字从1推送到100.这个observable由AsyncClass订阅,class AsyncClass { private readonly IConnectableObservable<int> _source; private readonly IDisposable _sourceDisposeObj; public IObservable<string> _asyncOpObservable; public AsyncClass(IConnectableObservable<int> source) { _source = source; _sourceDisposeObj = _source.Subscribe( ProcessArguments, ExceptionHandler, Completed ); _source.Connect(); } private void Completed() { Console.WriteLine("Completed"); Console.ReadKey(); } private void ExceptionHandler(Exception exp) { throw exp; } private void ProcessArguments(int evtArgs) { Console.WriteLine("Argument being processed with value: " + evtArgs); //_asyncOpObservable = LongRunningOperationAsync("hello").Publish(); // not going to work either since this creates a new observable for each value from main observer } // http://rxwiki.wikidot.com/101samples public IObservable<string> LongRunningOperationAsync(string param) { // should not be creating an observable here, rather 'pushing' values? return Observable.Create<string>( o => Observable.ToAsync<string, string>(DoLongRunningOperation)(param).Subscribe(o) ); } private string DoLongRunningOperation(string arg) { return "Hello"; } } 为每个数字运行一个长时间运行的进程。完成这个新的异步过程后,我希望能够“推送”到2个订阅者,这将使用这个新值做一些事情。

我的尝试在下面的源代码中得到了评论。

AsyncClass:

static void Main(string[] args)
        {
            var source = Observable
                            .Range(1, 100)
                            .Publish();

            var asyncObj = new AsyncClass(source);
            var _asyncTaskSource = asyncObj._asyncOpObservable;

            var ui1 = new UI1(_asyncTaskSource);
            var ui2 = new UI2(_asyncTaskSource);
        }

主:

 class UI1
    {
        private IConnectableObservable<string> _asyncTaskSource;
        private IDisposable _taskSourceDisposable;

        public UI1(IConnectableObservable<string> asyncTaskSource)
        {
            _asyncTaskSource = asyncTaskSource;
            _asyncTaskSource.Connect();
            _taskSourceDisposable = _asyncTaskSource.Subscribe(RefreshUI, HandleException, Completed);
        }

        private void Completed()
        {
            Console.WriteLine("UI1: Stream completed");
        }

        private void HandleException(Exception obj)
        {
            Console.WriteLine("Exception! "+obj.Message);
        }

        private void RefreshUI(string obj)
        {
            Console.WriteLine("UI1: UI refreshing with value "+obj);
        }
    }

UI1(和UI2,它们基本相同):

{{1}}

这是我与Rx的第一个项目,所以让我知道我是否应该以不同的方式思考。任何帮助将非常感谢!

1 个答案:

答案 0 :(得分:1)

我打算让你知道你应该以不同的方式思考... :)除了轻浮之外,这看起来像是面向对象和功能反应风格之间的错误碰撞。

目前还不清楚数据流的时间要求和结果缓存的要求是什么 - 使用PublishIConnectableObservable有点困惑。我猜你想要避免2个下游订阅导致处理重复的值?我的一些答案就是基于这个前提。使用Publish()可以通过允许多个订阅者共享单个源的订阅来实现此目的。

Idiomatic Rx希望您尝试保持功能风格。为此,您希望将长时间运行的工作作为一项功能提供。所以,让我们说,不要试图将AsyncClass逻辑直接连接到Rx链中作为一个类,你可以将它作为一个函数来表示,如下所示:

async Task<int> ProcessArgument(int argument)
{
    // perform your lengthy calculation - maybe in an OO style,
    // maybe creating class instances and invoking methods etc.
    await Task.Delay(TimeSpan.FromSeconds(1));
    return argument + 1;
}

现在,您可以构建一个完整的Rx可观察链来调用此函数,并且通过使用Publish().RefCount(),您可以避免多个订阅者导致重复工作。注意这也是如何区分问题的 - 处理值的代码更简单,因为重用是在别处处理的。

var query = source.SelectMany(x => ProcessArgument(x).ToObservable())
                  .Publish().RefCount();

通过为订阅者创建单个链,只有在订阅时才需要开始工作。我已使用Publish().RefCount() - 但如果您想确保第二个及后续订阅者不会错过任何值,您可以使用Replay(简单)或使用Publish()然后{{ 1}} - 但是你需要在个人订阅者代码之外的Connect逻辑,因为你只需要在所有订阅者订阅时调用它一次。