1

We're working with ReactiveExtensions for .Net

We schedule code to run on the Thread Pool like this:

IDisposable myDisposable = Scheduler.Default.Schedule(() =>
{
    int count = 0;
    while (true)
    {
        Console.WriteLine(++count);
        Thread.Sleep(500);
    }
});

Console.ReadKey();

myDisposable.Dispose(); // WHY THIS DO NOTHING?!?!

Console.ReadKey();

As you can see this is a test code that run on a console application.

When I dispose the scheduled code, the code keeps running!

I have no flag that I can check, I cannot add an if statement to stop the scheduled code by myself.

Someone can explain this? Why do I get an IDisposable if it doesn't work? and why I'm not getting some kind of flag (CancellationToken??) for checking inside my code so I can terminate its running in a timely manner?

Thanks!

Yitzchak
  • 3,303
  • 3
  • 30
  • 50
  • 1
    Where do you declare/set `scheduled`? – Fildor Oct 29 '18 at 14:52
  • 1
    Scheduling code on a scheduler is not the same thing as creating a task. Actions scheduled on a thread pool can't be cancelled while running; the disposable you get back only prevents future recursive scheduling calls from that method. The way to make tasks and Rx interact properly is not always obvious, but it looks like in this case you want a task. (You should have a `ScheduleAsync` for this purpose.) – Jeroen Mostert Oct 29 '18 at 14:59
  • @dymanoid thanks I resolved it, bad copy paste – Yitzchak Oct 29 '18 at 15:00
  • @JeroenMostert but my code is synchronous – Yitzchak Oct 29 '18 at 15:03

2 Answers2

2

The issue you're facing is that once your thread gets inside the while (true) loop it is trapped and can never end. There is nothing magical about .Dispose() that will stop a thread.

In fact, the disposable is already disposed before you call .Dispose(). Since you've used an overload of .Schedule that only runs once the subscription is disposed as soon as the code starts.

What you need to do is use an overload that supports rescheduling. Luckily there's one already there. Try this code:

IDisposable myDisposable = Scheduler.Default.Schedule(0, TimeSpan.Zero, (count, reschedule) =>
{
    Console.WriteLine(count);
    reschedule(count + 1, TimeSpan.FromSeconds(0.5));
});

This works exactly as you want it to - it correctly disposes.

Now, having said that, you're working with Rx, so you might as well just use the standard observable operators. This works too:

IDisposable myDisposable =
    Observable
        .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(0.5))
        .Subscribe(count => Console.WriteLine(count));

Simple.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Thanks for the detailed answer (you always write long answer). This is not a solution for me because the while (true) is an example for long running blocking code that is not recursive, so I cannot rewrite it with the “reschedule” solution – Yitzchak Oct 30 '18 at 18:39
  • From your answer I understand that the IDisposable that returned from Schedule() is useless for me. This is the answer I was looking for. I still don’t really understand what is it for?? – Yitzchak Oct 30 '18 at 18:52
  • @Yitzchak - The disposable is primarily there for canceling recurring schedules - like my first example. – Enigmativity Oct 30 '18 at 22:24
1

The reason your code is continuing is that you are expecting a Dispose() call to magically exit a hard loop (the while(true) part).

You could set a variable - and in your dispose method, unset the variable, e.g.:

_isRunning = true;
while(_isRunning)
{
    // do stuff
}

Then in your dispose method, call:

_isRunning = false;

I posted a way to achieve .Net Task scheduling in my answer here, which might help: Best way to create a "run-once" time delayed function in C#

James Harcourt
  • 6,017
  • 4
  • 22
  • 42
  • Hi, thanks for your answer. In my real code I don’t have a Dispose method. I’m using the IScheduler implementation because I can mock it in tests, an I thought I can use it’s Dispose for stopping the action from running. From your answer I understand that the IDisposable that being returned from IScheduler.Schedule() is useless in the case of Scheduler.Default. And that’s is my answer. – Yitzchak Oct 30 '18 at 18:50