我想要将基于事件的API(Geolocator)转换为 Rx 。
问题是某些操作需要取消订阅所有事件,并且我不想将该burdon传递给Rx API的用户。
因此,用户将订阅一些可观察对象,当订阅事件时,它们将发布到那些可观察对象。
最好的方法是什么?
我想过创建一个用户订阅的主题,然后通过另一组可观察对象将这些主题发布给那些主题。
这是最好的方法吗?如果是这样,怎么样?
答案 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;
}
}
现在,这是解决方案的核心。我们将使用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()`主题。
最好的部分是,每次Suspend
,Stop
或Stopped
时,此技术都会导致Rx做正确的事情并正确取消订阅基础事件来源。另请注意,即使您再次Switch
,static 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();