2

I want to send a start pulse to my server when the first client connects, and a finish pulse when the last one disconnects.

public class MyAdapter : IObservable<MyType> {

    IObservable<MyType> MyObservable = BuildMyObservable()
        .Initially(Start) // <- this method doesn't exist
        .Finally(Stop).Publish().RefCount().Subscribe(observer);

    public IDisposable Subscribe(IObserver<MyType> observer);
        return MyObservable.Subscribe(observer)
    }

    async Task Start() { /* start UDP stream */ }
    async Task Stop() { /* stop UDP stream */ }
    IObservable<MyType> BuildMyObservable() { /* wire up stuff */ }

}

In the above method, am I looking for a function Initially that doesn't exist, or am I just overlooking it?

shannon
  • 8,664
  • 5
  • 44
  • 74

3 Answers3

3

You could simply treat the Task like any other sequence, and just chain it into the query

Start().ToObservable()
  .SelectMany(_=>MyObservable)
  .Finally(Stop)

As a separate note, I would encourage you to avoid crafting API's that have methods that take the format IDisposable Subscribe(IObserver<MyType> observer). This takes away the power of Rx from your consumers. Instead, just expose the IObservable<T>, as it has the Subscribe method already. Now your consumer can chain your sequence, compose it, choose the correct concurrency/threading model (with ObserveOn/SubscribeOn), and apply their Error handling requirements.

Also as a last note, it is a bit strange to publish-refcount a sequence that is the result of a method call. It is even more strange to publish-refcount when your method only allows the consumer to provide one consumer. Assuming you change your method signature to the recommended/standard approach, then I would also suggest that you either remove the Publish().Refcount() code as it would highly unlikely for the consumer to cached the result and reuse it, v.s. recalling the method. Or you can keep the method (even better change it to a property) and then you internally cache the published sequence.

public class MyServiceThing
{
    private readonly IObservable<MyType> _myObservable;

    public MyServiceThing()
    {
        _myObservable = Start().ToObservable()
            .SelectMany(_=>/*The thing that defines your observable sequence...*/)
            .Finally(Stop)
            .Publish().RefCount();
    }

    public IObservable<MyType> MyObservable()
    {
        return _myObservable;
    }
    //OR
    //public IObservable<MyType> MyObservable() { get { return _myObservable; } }

    private async Task Start() {}
    private async Task Stop() {}
}
Lee Campbell
  • 10,631
  • 1
  • 34
  • 29
  • Thank you. Your confusion regarding the specificity of the API and the `Publish().Refcount()` arises because you misinterpreted the intent of the code. This is my internal wiring I'm using to invert some dependencies (for clarity, not composability), and has no effect on API. Also, I might mention that I think the SelectMany ultimately probably performs just a little more poorly that some examples that hand off the observer directly, which in this case I care about because I'm using Rx to simplify some high-volume network streaming (high-bandwidth radio) internally. Make sense? – shannon Jun 20 '16 at 03:06
  • _I think the SelectMany ultimately probably performs just a little more poor_. Ah, I would validate (measure) that assumption, because it sounds very spurious and I am guessing it is unfounded. I have done plenty of high volume work with Rx, and `SelectMany` has never been the cause of issues. lol – Lee Campbell Jun 20 '16 at 03:11
  • Not every performance estimate has to be validated. All other things being equal, no point in not following instinct! Note my reasoning that I further clarified after you copied. – shannon Jun 20 '16 at 03:12
  • Also, note my pub/refcount is *necessarily* associated with the concept of start/stop for the stream on this device, so IMO it's entirely schematic to include here, along with "Initially/Finally". If a consumer sends a stop request the remote is effectively stopping it for ALL subscribers. I'd love to hear, though, if that still doesn't make sense or I'm overlooking something. – shannon Jun 20 '16 at 03:20
  • In your example above, `MyServiceThing` is actually conceptually closer to `MyStateAdapterThing : IObservable` in my code. The reason for this is that it is implemented via UDP, which intrinsically results in separate control and data channels. – shannon Jun 20 '16 at 03:47
  • Wow lots of things we dont see eye to eye on but that is cool. "no point in not following instinct", except when it is wrong and it is really easy to validate, cause it is just code. I would strongly suggest not implementing `IObserverable`, but composing it instead. But that is old tired ground. – Lee Campbell Jun 20 '16 at 03:58
  • Lastly, how is your `Stop` method stopping other consumers? They appear completely unrelated.Two independent consumers can subscribe to your system, both will be given different instances and both will invoke the `Start` method. When one unsubscribes, it will not unsubscribe the other, but it will execute the Stop method. – Lee Campbell Jun 20 '16 at 03:58
  • Because the whole point of the stop method is to send a "StopTransmission" command to the remote. Why would the remote (let's call it a "server" for simplicity) be responsible for tracking threads on a "client" machine? It shouldn't. Also, when one "client" unsubscribes, it should neither send a stop pulse to the "server", nor end all other subscriptions. – shannon Jun 20 '16 at 04:07
  • My comment on "no point in not following instinct" is that we all allocate our time as priorities allow. We make informed decisions for 99% of our daily activities. I get out of bed every day assuming the floor is where I left it, because that assumption is usually accurate. All other things being equal, if there's no argument for doing otherwise, I avoid adding indirection to my code because that is typically wise. Are you suggesting I should suspect using SelectMany would radically improve my performance and therefore I should evaluate it? – shannon Jun 20 '16 at 04:09
  • Also, I'd love a reference to either a rich discussion or a well-researched article specifically regarding composing IObservable, if that's what you mean (vs. composition in OOP in-general). I'm always interested in learning! – shannon Jun 20 '16 at 04:29
  • It appears as you code is written, one client unsubscribing would execute that `Stop` action even if other clients have called your implementation of `Subscribe` and are thus subscribed. (just looking at your OP) – Lee Campbell Jun 20 '16 at 04:46
  • Ah, I see. Yes, I agree. My code above is a drastic simplification of the adapter. – shannon Jun 20 '16 at 04:47
  • 1
    It's still ugly, but it is ugly in ways other than that which you are commenting on ;) – shannon Jun 20 '16 at 04:47
2

I guess you are looking for a .Net equivalent of doOnSubscribe from RxJava. It does not exist out of the box.

What you can do, is wrap your MyObservable in Observable.Defer function, and call your server inside the Defer. You can play with the below code to see what I mean:

class Program
{
    static void Main(string[] args)
    {
        var source = Observable.Interval(TimeSpan.FromSeconds(1));

        var published = Observable.Defer(() =>
        {
            Console.WriteLine("Start"); // Here, you post "Start" to server
            return source;
        })
        .Finally(() => Console.WriteLine("End")) // Here, you post "End"
        .Publish()
        .RefCount();

        Console.ReadLine();
        var disposable = published.Subscribe(x => Console.WriteLine("First " + x));
        Console.ReadLine();
        var disposable2 = published.Subscribe(x => Console.WriteLine("Second " + x));
        Console.ReadLine();
        disposable.Dispose();
        Console.ReadLine();
        disposable2.Dispose();
        Console.ReadLine();
        published.Subscribe(x => Console.WriteLine("Third " + x));
        Console.ReadLine();
    }
}

For more explanation about deferring, see this excellent blogpost.

pmbanka
  • 1,902
  • 17
  • 24
0

Using pmbanka's correct answer, I added an Observable extension for this.

public static IObservable<T> Initially<T>(this IObservable<T> resultingObservable, Func<Task> initialAction) {
    return Observable.Defer(async () =>
    {
        await initialAction();
        return resultingObservable;
    });
}

public static IObservable<T> Initially<T>(this IObservable<T> resultingObservable, Action initialAction) {
    return Observable.Defer(() =>
    {
        Action();
        return resultingObservable;
    });
}

It's as-yet untested, and may not be optimal regarding async/task lambdas.

I guess the following does the same:

public static IObservable<T> Initially<T>(this IObservable<T> resultingObservable, Func<Task> initialAction) {
    return Observable.Create(async (IObserver<T> observer) =>
    {
        await initialAction();
        return resultingObservable.Subscribe(observer);
    });
}
shannon
  • 8,664
  • 5
  • 44
  • 74