Marcelo de Aguiar
Marcelo de Aguiar

Reputation: 1442

Why this CancellationDisposable never get canceled on Observable.Dispose()?

Im using RxFramework within an WinForms app. Im trying to run an Observable async and using the CancellationDisposable to cancel the operation when user clicks a button. but it is not working!

Suppose I have a form with 2 buttons and a ProgressBar. Button1_click subscribe to the observer on a new Thread. Button2_click is then pressed right after to cancel the operation. Why cancel.Token.IsCancellationRequested is never true?

private IDisposable obs = null;
private void button1_Click(object sender, EventArgs e) {
    var countObserver = Observable.Create<int>(observer => {
        var cancel = new CancellationDisposable();

        if (!cancel.Token.IsCancellationRequested) {
            //Step 1 of a long running process using lot of resources...
            observer.OnNext(1);
        }
        if (!cancel.Token.IsCancellationRequested) {
            //Step 2 of a long running process using lot of resources...
            observer.OnNext(1);
        }
        if (!cancel.Token.IsCancellationRequested) {
            //Step 3 of a long running process using lot of resources...
            observer.OnNext(1);
        }
        observer.OnCompleted();

        return cancel;
    });

    obs = countObserver
        .ObserveOn(new ControlScheduler(this))
        .SubscribeOn(Scheduler.ThreadPool)
        .Subscribe(i => {
            //Update a progress bar here...
        });

}

private void button2_Click(object sender, EventArgs e) {
    if (obs != null)
        obs.Dispose();
}

Upvotes: 2

Views: 819

Answers (2)

Pavel Gatilov
Pavel Gatilov

Reputation: 7661

It happens so because the lambda you pass to Observable.Create does not return the CancellationDisposable until it goes through all the steps. So, the order of actions is the following:

  1. You call Subscribe
  2. UI thread blocks
  3. On a ThreadPool thread, the execution enters the lambda
  4. CancellationDisposable is created
  5. You check for cancellation several times and execute the whole process. During this, your progress bar is updated.
  6. cancel is returned from lambda
  7. UI thread is released and obs gets its value
  8. You click Button2 and call obs.Dispose
  9. cancel gets cancel.Token.IsCancellationRequested=true. But nothing happens since everything has already happened.

Here's the fixed code:

private void button1_Click(object sender, EventArgs e) {
  var countObserver = Observable.Create<int>(observer => {
    var cancel = new CancellationDisposable();

    // Here's the magic: schedule the job in background and return quickly
    var scheduledItem = Scheduler.ThreadPool.Schedule(() =>
    {
      if (!cancel.Token.IsCancellationRequested) {
        //Step 1 of a long running process using lot of resources...
        observer.OnNext(1);
      }
      if (!cancel.Token.IsCancellationRequested) {
        //Step 2 of a long running process using lot of resources...
        observer.OnNext(1);
      }
      if (!cancel.Token.IsCancellationRequested) {
        //Step 3 of a long running process using lot of resources...
        observer.OnNext(1);
      }
      observer.OnCompleted();
    });

    return new CompositeDisposable(cancel, scheduledItem);
});

obs = countObserver
    .ObserveOn(new ControlScheduler(this))
    .Subscribe(i => {
        //Update a progress bar here...
    });

}

Upvotes: 3

Ana Betts
Ana Betts

Reputation: 74654

How about this instead, there are a number of bugs with the code above, but there's actually a better way to do this altogether (Warning: Coding in TextArea ahead):

countObservable = Observable.Timer(new ControlScheduler(this));

var buttonObservable = Observable.FromEventPattern<EventArgs>(
    x => button1.Click += x, x => button1.Click -= x);

countObservable
    .TakeUntil(buttonObservable)
    .Subscribe(x => /* Do stuff */);

Upvotes: 1

Related Questions