0

I need to recollect some data calling to a method is connecting to a webservice.

problem: Imagine I need to update the content text of a label control according to this remote gathered information. Until all this data is recollected I'm not going to be able to show the label.

desired: I'd like to first show the label with a default text, and as I'm receiving this information I want to update the label content (please, don't take this description as a sucked code, I'm trying to brief my real situation).

I'd like to create an observable sequence of these methods. Nevertheless, these method have not the same signature. For example:

int GetInt() {
    return service.GetInt();
}

string GetString() {
    return service.GetString();
}

string GetString2 {
    return service.GetString2();
}

These methods are not async.

  1. Is it possible to create an observable sequence of these methods?

  2. How could I create it?

  3. Nevertheless, which's the best alternative to achieve my goal?

Jordi
  • 20,868
  • 39
  • 149
  • 333

2 Answers2

2

Creating custom observable sequences can be achieved with the Observable.Create. An example using your requirements is shown below:

private int GetInt()
{
    Thread.Sleep(1000);

    return 1;
}

private string GetString()
{
    Thread.Sleep(1000);

    return "Hello";
}

private string GetString2()
{
    Thread.Sleep(2000);

    return "World!";
}

private IObservable<string> RetrieveContent()
{
    return Observable.Create<string>(
        observer =>
        {
            observer.OnNext("Default Text");

            int value = GetInt();

            observer.OnNext($"Got value {value}. Getting string...");

            string string1 = GetString();

            observer.OnNext($"Got string {string1}. Getting second string...");

            string string2 = GetString2();

            observer.OnNext(string2);
            observer.OnCompleted();

            return Disposable.Empty;
        }
    );
}

Note how I have emulated network delay by introducing a Thread.Sleep call into each of the GetXXX methods. In order to ensure your UI doesn't hang when subscribing to this observable, you should subscribe as follows:

IDisposable subscription = RetrieveContent()
  .SubscribeOn(TaskPoolScheduler.Default)
  .ObserveOn(DispatcherScheduler.Current)
  .Subscribe(text => Label = text);

This code uses the .SubscribeOn(TaskPoolScheduler.Default) extension method to use a TaskPool thread to start the observable sequence and will be blocked by the calls the Thread.Sleep but, as this is not the UI thread, your UI will remain responsive. Then, to ensure we update the UI on the UI thread, we use the ".ObserveOn(DispatcherScheduler.Current)" to invoke the updates onto the UI thread before setting the (data bound) Label property.

Hope this is what you were looking for, but leave a comment if not and I'll try to help further.

ibebbs
  • 1,963
  • 2
  • 13
  • 20
  • You should consider that anytime you do a `return Disposable.Empty;` you are probably doing something wrong. – Enigmativity Jan 19 '17 at 22:23
  • I'm getting a `System.InvalidOperationException` exception: `The current thread has no Dispatcher associated with it.` The last line of the stacktrace is at `System.Reactive.Concurrency.DispatcherScheduler.get_Current()`. I don't quite figure out how to handle it. Any ideas? – Jordi Jan 20 '17 at 08:31
  • @Enigmativity - it's probably worth expanding on your comment here. While I don't necessarily agree that "anytime you do a return Disposable.Empty; you are probably doing something wrong" it is certinaly worth considering whether you might want to perform some additional behaviour when the subscription is disposed (i.e. perform a cancellation). However, this was not requested by the OP so I omitted for brevity. – ibebbs Jan 20 '17 at 09:51
1

I would look at creating a wrapper class for your service to expose the values as separate observables.

So, start with a service interface:

public interface IService
{
    int GetInt();
    string GetString();
    string GetString2();
}

...and then you write ServiceWrapper:

public class ServiceWrapper : IService
{
    private IService service;
    private Subject<int> subjectGetInt = new Subject<int>();
    private Subject<string> subjectGetString = new Subject<string>();
    private Subject<string> subjectGetString2 = new Subject<string>();

    public ServiceWrapper(IService service)
    {
        this.service = service;
    }

    public int GetInt()
    {
        var value = service.GetInt();
        this.subjectGetInt.OnNext(value);
        return value;
    }

    public IObservable<int> GetInts()
    {
        return this.subjectGetInt.AsObservable();
    }

    public string GetString()
    {
        var value = service.GetString();
        this.subjectGetString.OnNext(value);
        return value;
    }

    public IObservable<string> GetStrings()
    {
        return this.subjectGetString.AsObservable();
    }

    public string GetString2()
    {
        var value = service.GetString2();
        this.subjectGetString2.OnNext(value);
        return value;
    }

    public IObservable<string> GetString2s()
    {
        return this.subjectGetString2.AsObservable();
    }
}

Now, assuming that you current service is called Service, you would write this code to set things up:

IService service = new Service();
ServiceWrapper wrapped = new ServiceWrapper(service); // Still an `IService`

var subscription =
    Observable
        .Merge(
            wrapped.GetInts().Select(x => x.ToString()),
            wrapped.GetStrings(),
            wrapped.GetString2s())
        .Subscribe(x => label.Text = x);

IService wrappedService = wrapped;

Now pass wrappedService instead of service to your code. It's still calling the underlying service code so no need for a re-write, yet you still are getting the observables that you want.

This is effectively a gang of four decorator pattern.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Really awesome! – Jordi Jan 20 '17 at 08:19
  • [Many](http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx) [would](https://social.msdn.microsoft.com/Forums/en-US/bbf87eea-6a17-4920-96d7-2131e397a234/why-does-emeijer-not-like-subjects) [say](http://stackoverflow.com/questions/14396449/why-are-subjects-not-recommended-in-net-reactive-extensions) that using Subjects in this manner is introducing a lot of unnecessary state and overhead. – ibebbs Jan 20 '17 at 10:08
  • @Enigmativity - I assume the first line of your code is meant to read ```IService service = new Service()``` as otherwise the consumer of ```wrappedService``` will receive a null reference exception when calling any of the GetXXX methods? – ibebbs Jan 20 '17 at 10:22
  • @ibebbs - Yes, you're right. I had that in my test code and forgot to edit it out when I posted. – Enigmativity Jan 20 '17 at 10:24