2

Background

I am writing some software that does the following:

  1. The user clicks on "start".
  2. A task is started that performs some work and launches events to update the GUI.
  3. An observable consumes the events from the task, and prints data to a rich-text-box in the GUI.

Everything works alright when I click on "START" the first time, but not after that. The first time I click on start, I get an output that looks like:

Clicking START once

This looks alright, nothing wrong here. However, when I click on START the second time I get the following output.

Clicking START again

Now, I believe I know why this might be happening. From what I can tell, my observer never unsubscribed from the first time I clicked on START and thus everything gets printed twice. When clicking on the start button, here is what happens:

    /// <summary>
    /// Starts the test.
    /// </summary>
    /// <param name="sender">The "start" button.</param>
    /// <param name="e">Clicking on the "start" button.</param>
    private void button_go_Click(object sender, RoutedEventArgs e)
    {
        var uiContext = SynchronizationContext.Current;
        var results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
            handler => (s, a) => handler(s, a),
            handler => this.myTest.Results += handler,
            handler => this.myTest.Results -= handler)
            .ObserveOn(uiContext)
            .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

        // start running the test
        this.runningTest = new Task(() => { this.myTest.Run(); });
        this.runningTest.Start();

        // block on purpose, wait for the task to finish
        this.runningTest.Wait();
    }

I am new to reactive extensions in .NET (have been using it for less than one hour). I am essentially using one of Stephen Cleary's examples from the Concurrency in C# cookbook in order to get started here. Despite all of this, I believe I sort of know what the problem is...

Proposed Plan

If I could just unsubscribe from the observable, then I think that this problem would go away. I think it's a little more complicated that that though, based on the reactive patterns used at ReactiveX.io it sounds like I should actually be smartly observing the task only for the during in which it actually exists then unsubscribing naturally. This is as far as I have come with debugging this problem, and I am not 100% sure that this unsubscribing thing would actually fix the problem.

Question

Is there some way to unsubscribe from the observable? Or, is there a way that I could observe my task only for the duration that it exists?

Snoop
  • 1,046
  • 1
  • 13
  • 33
  • Subscribe() returns an IDisposable object. In your case, you called your variable "results". By doing results.Dispose(), you unsubscribe from the observable. – Absolom Apr 12 '16 at 17:38
  • @Absolom sounds strange, are you suggesting that's what I do? Call `Dispose` just to unsubscribe from something? I thought that `Dispose` was supposed to be reserved for un-managed resources... – Snoop Apr 12 '16 at 17:40
  • @Absolom you know what seemed to work? Continuing with the disposal of the task... So I do a `ContinueWith` and I specify `results.Dispose()` (from the current thread synchronization context)... Is that a proper way to do it? – Snoop Apr 12 '16 at 17:47
  • In this case, the developers decided that calling Dispose means that you don't need the subscription anymore. Here is a good [link](http://www.introtorx.com/content/v1.0.10621.0/03_LifetimeManagement.html#Unsubscribing) that describes the lifetime of observables. – Absolom Apr 12 '16 at 19:29

1 Answers1

5

Rx uses the IDisposable interface to enable you to unsubscribe from an observable subscription that hasn't yet naturally ended. If your observable sends a OnCompleted or OnError notification then the subscription is automatically disposed for you.

One clear advantage to this approach is that you can create a single CompositeDisposable to aggregate all of your subscriptions to enable a single unsubscribe. This is far better than hodge-podge of detaches required for removing event handlers.

In your code you're not ending the subscription so for each click on button_go you're creating a new subscription.

There are four solutions you can use, each somewhat different.

(1)

Remember, the key to needing to call .Dispose() is if your observable subsription hasn't naturally ended and you want it to end. So, you can simply add a .Take(1) to your query to make it naturally end after one value is produced.

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    var results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
        handler => (s, a) => handler(s, a),
        handler => this.myTest.Results += handler,
        handler => this.myTest.Results -= handler)
        .Take(1)
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

The subscription is then automatically disposed for you.

(2)

You could use SerialDisposable to manage each subscription. The MSDN documentation describes it as:

Represents a disposable whose underlying disposable can be swapped for another disposable which causes the previous underlying disposable to be disposed.

Your code then would look like this:

private SerialDisposable _results = new SerialDisposable();

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    _results.Disposable = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
        handler => (s, a) => handler(s, a),
        handler => this.myTest.Results += handler,
        handler => this.myTest.Results -= handler)
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

(3)

You could always make sure that you only ever create the subscription once.

private IDisposable _results = null;

private void button_go_Click(object sender, RoutedEventArgs e)
{
    if (_results == null)
    {
        var uiContext = SynchronizationContext.Current;
        _results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
            handler => (s, a) => handler(s, a),
            handler => this.myTest.Results += handler,
            handler => this.myTest.Results -= handler)
            .ObserveOn(uiContext)
            .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));
    }

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

(4)

The final approach is where the entire operation is wrapped up in an observable and is instigated for each new subscription. This is the correct way to work with Rx. Observables should hold their own state so that subscriptions can be 100% independent of each other.

Now, your code uses this.myTest & this.runningTest so it clearly has state. You should try to remove these. But without doing so your code would look like this:

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    Observable
        .Create<System.Reactive.EventPattern<TestResultArgs>>(o =>
        {
            var subscription =
                Observable
                    .FromEventPattern<TestResultHandler, TestResultArgs>(
                        h => this.myTest.Results += h,
                        h => this.myTest.Results -= h)
                    .Take(1)
                    .Subscribe(o);
            this.runningTest = new Task(() => { this.myTest.Run(); });
            this.runningTest.Start();
            return subscription;
        })
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

Ideally you should incorporate the creation and destruction of myTest inside the observable.

So, I would be inclined to do something like this:

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    Observable
        .Create<System.Reactive.EventPattern<TestResultArgs>>(o =>
        {
            var myTest = new MyTest();
            var subscription =
                Observable
                    .FromEventPattern<TestResultHandler, TestResultArgs>(
                        h => myTest.Results += h,
                        h => myTest.Results -= h)
                    .Take(1)
                    .Subscribe(o);
            myTest.Run();
            return subscription;
        })
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));
}

This last one is really the answer to your question: "Or, is there a way that I could observe my task only for the duration that it exists?"

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • You are the man, I will give these a try tomorrow and get back to you. Thank you very much for the thought and effort that you put into this answer. – Snoop Apr 13 '16 at 03:06
  • @StevieV - No worries. Try getting the last one to work for you. Rx works best when you avoid external state. – Enigmativity Apr 13 '16 at 03:24
  • Is there any way to get #4 (part 2) not to block, and `Take` until the `Test.Run()` is finished? – Snoop Apr 13 '16 at 12:26
  • @StevieV - Just pop back your code, but use a local variable - `var running = new Task(() => { this.myTest.Run(); }); running.Start();` – Enigmativity Apr 13 '16 at 12:30
  • You see how there is a STOP button? I intend to use that to stop the running test/task. I think that if I were to make the task local, I would no longer be able to stop it. – Snoop Apr 13 '16 at 12:33
  • @StevieV - Sure, then make it the same as before, but make sure you can't click `Start` until the observable is finished. The pause is going to be tricky. That might require a bit of redesign. – Enigmativity Apr 13 '16 at 22:51