简体   繁体   English

使用IObservable而不是事件

[英]Use of IObservable instead of events

I've recently been reading about IObservable. 我最近一直在阅读有关IObservable的内容。 So far, i've looked at various SO questions, and watched a video on what they can do. 到目前为止,我已经查看了各种SO问题,并观看了他们可以做什么的视频。 The whole "push" mechanism I'm thinking is brilliant, but I'm still trying to figure out what exactly everything does. 我正在思考的整个“推动”机制非常棒,但我仍在努力弄清楚究竟是什么。 From my readings, I guess in a way an IObservable is something that can be 'watched', and IObservers are the 'watchers'. 从我的读数来看,我想在某种程度上IObservable可以被“监视”,而IObservers则是“观察者”。

So now I'm off to try and implement this in my application. 所以现在我要尝试在我的应用程序中实现它。 There are a few things I would like to nut out before I get started. 在我开始之前,有一些事情我想坚持下去。 I've seen that IObservable is the opposite of IEnumerable, however, I can't really see any places in my particular instance that I can incorporate into my app. 我已经看到IObservable与IEnumerable相反,但是,我无法在我的特定实例中看到任何可以合并到我的应用程序中的地方。

Currently, I make heavy use of events, so much that I can see the 'plumbing' is starting to get unmanageable. 目前,我大量使用事件,以至于我可以看到“管道”开始变得无法管理。 I would think, that IObservable can help me out here. 我想,IObservable可以帮助我。

Consider the following design, which is my wrapper around my I/O within my application (FYI, I typically have to deal with strings): 考虑以下设计,这是我的应用程序中的I / O包装(仅供参考,我通常需要处理字符串):

I have a base interface called IDataIO : 我有一个名为IDataIO的基本接口:

public interface IDataIO
{
  event OnDataReceived;
  event OnTimeout:
  event OnTransmit;
}

Now, I currently have three classes that implement this interface, each of these classes in some way utilize Async method calls, introducing some type of multithreaded processing: 现在,我目前有三个实现此接口的类,这些类中的每一个都以某种方式利用异步方法调用,引入了某种类型的多线程处理:

public class SerialIO : IDataIO;
public class UdpIO : IDataIO;
public class TcpIO : IDataIO;

There is a single instance of each of these classes wrapped up into my final class, called IO (which also implements IDataIO - adhering to my strategy pattern): 每个类的一个实例都包含在我的最终类中,称为IO(它也实现了IDataIO - 遵循我的策略模式):

public class IO : IDataIO
{
  public SerialIO Serial;
  public UdpIO Udp;
  public TcpIO Tcp;
}

I have utilized the strategy pattern to encapsulate these three classes, so that when changing between the different IDataIO instances at runtime makes it 'invisible' to the end user. 我已经利用策略模式来封装这三个类,这样当在运行时在不同的IDataIO实例之间进行更改时,它会让最终用户“看不见”。 As you could imagine, this has led to quite a bit of 'event plumbing' in the background. 你可以想象,这导致了背景中的“事件管道”。

So, how can I utilize 'push' notification here in my case? 那么,我如何在我的案例中使用“推送”通知? Instead of subscribing to events (DataReceived etc) I would like to simply push the data to anyone that's interested. 我想简单地将数据推送给任何感兴趣的人,而不是订阅事件(DataReceived等)。 I'm a bit unsure of where to get started. 我有点不确定从哪里开始。 I'm still trying to toy with the ideas/generic classes of Subject , and the various incarnations of this (ReplaySubject/AsynSubject/BehaviourSubject). 我仍然试图用Subject的想法/泛型类以及它的各种形式(ReplaySubject / AsynSubject / BehaviourSubject)来玩。 Could someone please enlighten me on this one (maybe with reference to my design)? 有人可以请教我这个(也许参考我的设计)? Or is this simply not an ideal fit for IObservable ? 或者这根本不适合IObservable

PS. PS。 Feel free to correct any of my 'misunderstandings' :) 随意纠正我的任何“误解”:)

Observables are great for representing streams of data, so your DataReceived event would model nicely to the observable pattern, something like IObservable<byte> or IObservable<byte[]> . Observable非常适合表示数据流,因此DataReceived事件可以很好地建模到可观察模式,例如IObservable<byte>IObservable<byte[]> You also get the added benefit of OnError and OnComplete which are handy. 您还可以获得OnErrorOnComplete的额外好处。

In terms of implementing it, it's hard to say for your exact scenario but we often use Subject<T> as the underlying source and call OnNext to push data. 在实现它方面,很难说出你的具体情况,但我们经常使用Subject<T>作为底层源并调用OnNext来推送数据。 Maybe something like 也许是这样的

// Using a subject is probably the easiest way to push data to an Observable
// It wraps up both IObservable and IObserver so you almost never use IObserver directly
private readonly Subject<byte> subject = new Subject<byte>();

private void OnPort_DataReceived(object sender, EventArgs e)
{
    // This pushes the data to the IObserver, which is probably just a wrapper
    // around your subscribe delegate is you're using the Rx extensions
    this.subject.OnNext(port.Data); // pseudo code 
}

You can then expose the subject through a property: 然后,您可以通过属性公开主题:

public IObservable<byte> DataObservable
{
    get { return this.subject; } // Or this.subject.AsObservable();
}

You can replace your DataReceived event on IDataIO with an IObservable<T> and have each strategy class handle their data in whichever manner they need and push off to the Subject<T> . 您可以使用IObservable<T>替换IDataIO上的DataReceived事件,并让每个策略类以他们需要的方式处理他们的数据,然后推送到Subject<T>

On the other side, whoever subscribes to the Observable is then able to either handle it like an event (just by using an Action<byte[]> ) or you can perform some really useful work on the stream with Select , Where , Buffer , etc. 另一方面,订阅Observable的任何人都能够像事件一样处理它(只需使用Action<byte[]> ),或者你可以使用SelectWhereBuffer ,在流上执行一些非常有用的工作。等等

private IDataIO dataIo = new ...

private void SubscribeToData()
{ 
    dataIo.DataObservable.Buffer(16).Subscribe(On16Bytes);
}

private void On16Bytes(IList<byte> bytes)
{
    // do stuff
}

ReplaySubject / ConnectableObservable s are great when you know your subscriber is going to be arriving late to the party but still needs to catch up on all of the events. ReplaySubject / ConnectableObservable很棒,当你知道你的订阅者将要迟到派对但仍需要赶上所有事件时。 The source caches everything it's pushed and replays everything for each subscriber. 源缓存它所推送的所有内容并为每个订户重放所有内容。 Only you can say whether that's the behaviour you actually need (but be careful because it will cache everything which is going increase your memory usage obviously). 只有你可以说这是否是你真正需要的行为(但要小心,因为它会缓存所有会增加你的内存使用量的东西)。

When I was learning about Rx I found http://leecampbell.blogspot.co.uk/ blog series on Rx to be very informative to understand the theory (the posts are a little dated now and the APIs have changed so watch out for that) 当我在学习Rx时,我发现http://licampbell.blogspot.co.uk/关于Rx的博客系列对理解这个理论非常有用(帖子现在有点过时,API已经改变了,所以要注意这个)

This is definitely an ideal case for observables. 这绝对是可观察的理想情况。 The IO class will probably see the most improvement. IO类可能会看到最大的改进。 To start with, lets change the interface to use observables and see how simple the combining class becomes. 首先,让我们更改界面以使用observables,看看组合类变得多么简单。

public interface IDataIO
{
    //you will have to fill in the types here.  Either the event args
    //the events provide now or byte[] or something relevant would be good.
    IObservable<???> DataReceived;
    IObservable<???> Timeout;
    IObservable<???> Transmit;
}

public class IO : IDataIO
{
    public SerialIO Serial;
    public UdpIO Udp;
    public TcpIO Tcp;

    public IObservable<???> DataReceived
    {
        get 
        {
            return Observable.Merge(Serial.DataReceived,
                                    Udp.DataReceived,
                                    Tcp.DataReceived);
        }
    }

    //similarly for other two observables
}

SIDE NOTE: You may notice that I changed the interface member names. 侧面注意:您可能会注意到我更改了接口成员名称。 In .NET events are typically named <event name> and the functions that raise them are called On<event name> . 在.NET中,事件通常命名为<event name> ,引发它们的函数称为On<event name>

For the producing classes, you have a few options that depend on the actual sources. 对于生产类,您有几个选项取决于实际来源。 Suppose you are using the .NET SerialPort class in the SerialIO and that DataReceived returns an IObservable<byte[]> . 假设您在SerialIO中使用.NET SerialPort类,并且DataReceived返回IObservable<byte[]> Since the SerialPort already has an event for data received, you can use that directly to make the observable you need. 由于SerialPort已经有一个接收数据的事件,您可以直接使用它来创建您需要的可观察数据。

public class SerialIO : IDataIO
{
    private SerialPort _port;

    public IObservable<byte[]> DataRecived
    {
        get
        {
            return Observable.FromEventPattern<SerialDataReceivedEventHandler,
                                               SerialDataReceivedEventArgs>(
                        h => _port.DataReceived += h,
                        h => _port.DataReceived -= h)
                   .Where(ep => ep.EventArgs.EventType == SerialData.Chars)
                   .Select(ep =>
                           {
                              byte[] buffer = new byte[_port.BytesToRead];
                              _port.Read(buffer, 0, buffer.Length);
                              return buffer;
                           });
        }
    }
}

For cases where you don't have an existing event source, you may need to use a subject as RichK suggested. 对于您没有现有事件源的情况,您可能需要使用RichK建议的主题。 His answer covers that usage pattern quite well, so I won't duplicate that here. 他的回答很好地涵盖了这种使用模式,所以我不会在这里复制它。

You did not show how you use this interface, but depending on the use case, it may make more sense to have other functions on these classes return IObservable s themselves and do away with these "events" entirely. 您没有展示如何使用此接口,但根据用例,在这些类上使用其他函数返回IObservable本身并完全取消这些“事件”可能更有意义。 With an event-based async pattern, you have to have events separate from the function you call to trigger the work, but with observables, you can return them from the function instead to make it more obvious what you are subscribing for. 使用基于事件的异步模式,您必须将事件与您调用的函数分开以触发工作,但是使用observable,您可以从函数返回它们,以使其更明显地为您所订阅的内容。 That approach also allows the observables returned from each call to send OnError and OnCompleted messages to signal the end of an operation. 该方法还允许从每次调用返回的observable发送OnErrorOnCompleted消息以指示操作结束。 Based on your use of a combining class, I don't expect this to be useful in this particular case, but it is something to keep in mind. 基于您对组合类的使用,我不认为这在这种特定情况下有用,但需要牢记这一点。

Use of IObservable instead of events 使用IObservable而不是事件

if only interested in the property change the nuget package rxx has: 如果只对该物业感兴趣,则改变nuget包rxx具有:

IObservable<string> obs=Observable2.FromPropertyChangedPattern(() => obj.Name)

(along with many others methods) (以及许多其他方法)


or if the event precludes property changes / looking to avoid implementation of INotifyPropertyChanged 或者如果事件排除了属性更改/希望避免实现INotifyPropertyChanged

class ObserveEvent_Simple
{
    public static event EventHandler SimpleEvent;
    static void Main()
    {          
       IObservable<string> eventAsObservable = Observable.FromEventPattern(
            ev => SimpleEvent += ev,
            ev => SimpleEvent -= ev);
    }
}

similar to u/Gideon Engelberth from http://rxwiki.wikidot.com/101samples#toc6 类似于u / Gideon Engelberth来自http://rxwiki.wikidot.com/101samples#toc6

covered by https://rehansaeed.com/reactive-extensions-part2-wrapping-events/ https://rehansaeed.com/reactive-extensions-part2-wrapping-events/提供


also this codeproject article is devoted to conversion of events to reactive events 此代码项目文章也致力于将事件转换为被动事件

https://www.codeproject.com/Tips/1078183/Weak-events-in-NET-using-Reactive-Extensions-Rx https://www.codeproject.com/Tips/1078183/Weak-events-in-NET-using-Reactive-Extensions-Rx

and also deals with weak subscriptions 并且还处理弱订阅

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM