Reputation: 1442
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
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:
Subscribe
ThreadPool
thread, the execution enters the lambdaCancellationDisposable
is createdcancel
is returned from lambdaobs
gets its valueButton2
and call obs.Dispose
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
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