Arnold Arnold - 5 months ago 16
C# Question

Dynamically Pause a Thread while in a Task

I was tasked to add a slidercontrol whose value is either to speed up or slow down the output of a what is in a task that generates values. I currently have a task that is running an infinite while loop and setting a property back to the ui. In addition, I am also hard coding a value in the thread sleep to control the speed of the output. I was wondering if I can make that variable in the Threas.Sleep instead of hard coding and be able to change that variable while the task is in progress. I have an idea I can cancel the the task when the slider change and restart it with the value from the slider control. Code for task is as so:

Task.Factory.StartNew(() =>
{
while (true)
{
App.Current.Dispatcher.Invoke(new Action(() =>
{
//Some number generated and set to a property
}));
Thread.Sleep(200);
}
});


Question is if I could change Thread.Sleep(200) to Thread.Sleep(SpeedVariable) that can be set somewhere else

Answer Source

You're better off avoiding the use of tasks and use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Threading" for the WPF bits, or "System.Reactive" for the standard bits).

It makes life much easier for this kind of thing. Start by defining static public volatile int SpeedVariable = 200; and then try this:

IDisposable subscription =
    Observable
        .Generate(0, x => true, x => x + 1, x => x,
            x => TimeSpan.FromMilliseconds(SpeedVariable))
        .ObserveOnDispatcher()
        .Subscribe(x =>
        {
            //Some number generated and set to a property
        });

You can stop the observable at any time by calling subscription.Dispose();.

You should even be able to use the value x in the .Subscribe method to compute the value of the slider. In this code the value starts as 0 and increments by 1 for each value produced.


Here's version of this code that can be run to see it works:

void Main()
{
    IDisposable subscription =
        Observable
            .Generate(0, x => true, x => x + 1, x => x,
                x => TimeSpan.FromMilliseconds(SpeedVariable))
            .ObserveOn(Scheduler.Default)
            .Subscribe(x => Console.WriteLine(x));

    Thread.Sleep(1000);
    SpeedVariable = 1000;
    Thread.Sleep(5000);
    SpeedVariable = 20;
    Thread.Sleep(500);
    subscription.Dispose();
}

static public volatile int SpeedVariable = 200;

Also, if you want to avoid using a static public volatile variable then this also works:

var speed = new ReplaySubject<int>(1);

IDisposable subscription =
    Observable
        .Generate(0, x => true, x => x + 1, x => x,
            x => TimeSpan.FromMilliseconds(speed.MostRecent(200).First()))
        .ObserveOn(Scheduler.Default)
        .Subscribe(x => Console.WriteLine(x));

Thread.Sleep(1000);
speed.OnNext(1000);
Thread.Sleep(5000);
speed.OnNext(20);
Thread.Sleep(500);
subscription.Dispose();

One possible complication with your existing code, the other answers so far, and my answers above, is that you can pause the thread for too long and there's no way to get thread to start again without waiting for the previous pause. Rx provides an easy way to get around this issue. Try this code:

var speed = new Subject<int>();

IDisposable subscription =
    speed
        .Select(s => Observable.Interval(TimeSpan.FromMilliseconds(s)))
        .Switch()
        .Select((x, n) => n)
        .ObserveOn(Scheduler.Default)
        .Subscribe(x => Console.WriteLine(x));

speed.OnNext(200);
Thread.Sleep(1000);
speed.OnNext(1000000); // wait 16.666 minutes
Thread.Sleep(5000);
speed.OnNext(20); // stop previous wait
Thread.Sleep(500);
subscription.Dispose();