订阅未来的可观察性

时间:2014-02-17 18:42:04

标签: system.reactive reactive-programming

我想要将基于事件的API(Geolocator)转换为 Rx

问题是某些操作需要取消订阅所有事件,并且我不想将该burdon传递给Rx API的用户。

因此,用户将订阅一些可观察对象,当订阅事件时,它们将发布到那些可观察对象。

最好的方法是什么?

我想过创建一个用户订阅的主题,然后通过另一组可观察对象将这些主题发布给那些主题。

这是最好的方法吗?如果是这样,怎么样?

2 个答案:

答案 0 :(得分:5)

关键问题是找到一种方法来保持Observer订阅流,同时拆除并替换底层源。让我们只关注单个事件源 - 您应该能够从中推断出来。

首先,这是一个我们可以使用的示例类,它具有单个事件SomeEvent,它遵循使用EventHandler<StringEventArgs>委托的标准.NET模式。我们将使用它来创建事件源。

注意我已拦截事件添加/删除处理程序,以便在Rx订阅和取消订阅事件时向您显示,并为类提供name属性以让我们跟踪不同的实例:

public class EventSource
{
    private string _sourceName;

    public EventSource(string sourceName)
    {
        _sourceName = sourceName;
    }

    private event EventHandler<MessageEventArgs> _someEvent;

    public event EventHandler<MessageEventArgs> SomeEvent
    {
        add
        {
            _someEvent = (EventHandler<MessageEventArgs>)
                Delegate.Combine(_someEvent, value);
            Console.WriteLine("Subscribed to SomeEvent: " + _sourceName);
        }
        remove
        {
            _someEvent = (EventHandler<MessageEventArgs>)
                Delegate.Remove(_someEvent, value);
            Console.WriteLine("Unsubscribed to SomeEvent: " + _sourceName);
        }

    }

    public void RaiseSomeEvent(string message)
    {
        var temp = _someEvent;
        if(temp != null)
            temp(this, new MessageEventArgs(message));
    }
}

public class MessageEventArgs : EventArgs
{
    public MessageEventArgs(string message)
    {
        Message = message;
    }

    public string Message { get; set; }   

    public override string ToString()
    {
        return Message;
    }
}

解决方案主要想法 - StreamSwitcher

现在,这是解决方案的核心。我们将使用Subject<IObservable<T>>来创建流的流。我们可以使用Observable.Switch()运算符仅将最新的流返回给Observers。这是实现,下面将使用一个使用示例:

public class StreamSwitcher<T> : IObservable<T>
{
    private Subject<IObservable<T>> _publisher;
    private IObservable<T> _stream;

    public StreamSwitcher()
    {
        _publisher = new Subject<IObservable<T>>();
        _stream = _publisher.Switch();
    }

    public IDisposable Subscribe(IObserver<T> observer)
    {
        return _stream.Subscribe(observer);
    }

    public void Switch(IObservable<T> newStream)
    {
        _publisher.OnNext(newStream);
    }

    public void Suspend()
    {
        _publisher.OnNext(Observable.Never<T>());
    }

    public void Stop()
    {
        _publisher.OnNext(Observable.Empty<T>());
        _publisher.OnCompleted();
    }
}

用法

使用此类,您可以在每次使用Switch方法启动事件时连接新流 - 只需将新事件流发送到Subject

您可以使用Suspend方法解除关联事件,该方法会Observable.Never<T>()Subject发送有效暂停事件流的Stop

最后,您可以通过调用Observable.Empty<T>() and来完全停止推送Switch OnComplete()`主题。

最好的部分是,每次SuspendStopStopped时,此技术都会导致Rx做正确的事情并正确取消订阅基础事件来源。另请注意,即使您再次Switchstatic void Main() { // create the switch to operate on // an event type of EventHandler<MessageEventArgs>() var switcher = new StreamSwitcher<EventPattern<MessageEventArgs>>(); // You can expose switcher using Observable.AsObservable() [see MSDN] // to hide the implementation but here I just subscribe directly to // the OnNext and OnCompleted events. // This is how the end user gets their uninterrupted stream: switcher.Subscribe( Console.WriteLine, () => Console.WriteLine("Done!")); // Now I'll use the example event source to wire up the underlying // event for the first time var source = new EventSource("A"); var sourceObservable = Observable.FromEventPattern<MessageEventArgs>( h => source.SomeEvent += h, h => source.SomeEvent -= h); // And we expose it to our observer with a call to Switch Console.WriteLine("Subscribing"); switcher.Switch(sourceObservable); // Raise some events source.RaiseSomeEvent("1"); source.RaiseSomeEvent("2"); // When we call Suspend, the underlying event is unwired switcher.Suspend(); Console.WriteLine("Unsubscribed"); // Just to prove it, this is not received by the observer source.RaiseSomeEvent("3"); // Now pretend we want to start events again // Just for kicks, we'll use an entirely new source of events // ... but we don't have to, you could just call Switch(sourceObservable) // with the previous instance. source = new EventSource("B"); sourceObservable = Observable.FromEventPattern<MessageEventArgs>( h => source.SomeEvent += h, h => source.SomeEvent -= h); // Switch to the new event stream Console.WriteLine("Subscribing"); switcher.Switch(sourceObservable); // Prove it works source.RaiseSomeEvent("3"); source.RaiseSomeEvent("4"); // Finally unsubscribe switcher.Stop(); } 一旦Subscribing Subscribed to SomeEvent: A 1 2 Unsubscribed to SomeEvent: A Unsubscribed Subscribing Subscribed to SomeEvent: B 3 4 Unsubscribed to SomeEvent: B Done! 也不再有事件流出。

这是一个示例程序:

Merge

这样输出如下:

Select

请注意,最终用户订阅时无关紧要 - 我事先做过,但他们可以随时订阅,然后他们就会开始获取活动。

希望有所帮助!当然,您需要将Geolocator API的各种事件类型组合到一个方便的包装器中 - 但这应该可以帮助您实现目标。

如果您想要使用此技术将多个事件组合成单个流,请查看CombineLatest之类的运算符,这需要您将源流投影为公共类型,{{1}}可能或类似{{1}}之类的东西 - 问题的这一部分不应该太棘手。

答案 1 :(得分:1)

这就是我想出来的。

我为我的API客户创建了两个主题来订阅:

private readonly Subject<Geoposition> positionSubject = new Subject<Geoposition>();
private readonly Subject<PositionStatus> statusSubject = new Subject<PositionStatus>();

我的API订阅的事件的可观察性:

private IDisposable positionObservable;
private IDisposable statusObservable;

当我想订阅活动时,我只是订阅了主题:

this.positionObservable = Observable
   .FromEvent<TypedEventHandler<Geolocator, PositionChangedEventArgs>, PositionChangedEventArgs>(
       conversion: handler => (s, e) => handler(e),
       addHandler: handler => this.geolocator.PositionChanged += handler,
       removeHandler: handler => this.geolocator.PositionChanged -= handler)
   .Select(e => e.Position)
   .Subscribe(
       onNext: this.positionSubject.OnNext,
       onError: this.positionSubject.OnError);

this.statusObservable = Observable
   .FromEvent<TypedEventHandler<Geolocator, StatusChangedEventArgs>, StatusChangedEventArgs>(
       conversion: handler => (s, e) => handler(e),
       addHandler: handler => this.geolocator.StatusChanged += handler,
       removeHandler: handler => this.geolocator.StatusChanged -= handler)
   .Select(e => e.Status)
   .Subscribe(
       onNext: this.statusSubject.OnNext,
       onError: this.statusSubject.OnError);

当我想取消订阅时,我只处理订阅:

this.positionObservable.Dispose();
this.statusObservable.Dispose();