-1

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

Arnold
  • 69
  • 1
  • 11

3 Answers3

1

Just use a global variable, and mark it volatile.

    static public volatile int WaitInterval = 200;

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

    protected mySlider_ValueChanged(object sender, SliderEventArgs e)
    {
        WaitInterval = mySlider.Value;
    }

Normally you'd have to put a mutex around a shared variable like this, but integer updates are automatically atomic in most situations. If you wanted to be absolutely safe, or if you plan to port to other operating systems, or if you want to use a long instead of an int, then you'd enclose the read and update within a lock block, or use Interlocked.Exchange, like this:

Interlocked.Exchange(ref WaitInterval, mySlider.Value);
John Wu
  • 50,556
  • 8
  • 44
  • 80
1

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();
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-1

John Wu's solution will work, but ideally you shouldn't be using Thread.Sleep, especially in a task as they're not always guaranteed to be running on a separate thread.

Instead, you can use the TPL's built in delay function:

static public volatile int WaitInterval = 200;
public void StartLoop()
{
    Task.Factory.StartNew(async () =>
    {
        while (true)
        {
            App.Current.Dispatcher.Invoke(new Action(() =>
            {
                    //Some number generated and set to a property
                }));
            await Task.Delay(WaitInterval);
        }
    });
}

protected mySlider_ValueChanged(object sender, SliderEventArgs e)
{
    WaitInterval = mySlider.Value;
}

As pointed out by Svick. When using async/await in task, use:

'Task.Run' rather than 'Task.Factory.StartNew'.

If you do encounter 'Task<Task<T>>' though, you can use the '.Unwrap()' method on a task instance.

svick
  • 236,525
  • 50
  • 385
  • 514
Clint
  • 6,133
  • 2
  • 27
  • 48
  • 1
    You shouldn't use `Task.Factory.StartNew` with `async`, it produces confusing `Task`. (For the record, I'm not the one who downvoted you.) – svick Jun 20 '17 at 14:45