在任务中动态暂停线程

时间:2017-06-20 00:11:40

标签: c# task-parallel-library

我的任务是添加一个slidercontrol,其值可以加快或减慢生成值的任务中的内容的输出。我目前有一个正在运行无限循环并将​​属性设置回ui的任务。另外,我也在线程睡眠中硬编码一个值来控制输出的速度。我想知道我是否可以在Threas.Sleep中创建该变量而不是硬编码,并且能够在任务正在进行时更改该变量。我有一个想法,我可以在滑块更改时取消该任务,并使用滑块控件中的值重新启动它。任务代码如下:

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

问题是我是否可以将Thread.Sleep(200)更改为可在其他位置设置的Thread.Sleep(SpeedVariable)

3 个答案:

答案 0 :(得分:2)

您最好避免使用任务并使用Microsoft的Reactive Framework(NuGet" System.Reactive.Windows.Threading"用于WPF位,或者" System.Reactive"用于标准位)。

这使这种事情变得更加容易。首先定义static public volatile int SpeedVariable = 200;,然后尝试:

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
        });

您可以随时致电subscription.Dispose();停止观察。

您甚至可以使用x方法中的值.Subscribe来计算滑块的值。在此代码中,值以0开头,并为每个生成的值增加1

此代码的此版本可以运行以查看其有效:

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;

此外,如果您想避免使用static public volatile变量,那么这也有效:

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();

现有代码的一个可能的复杂性,到目前为止的其他答案,以及我上面的答案,是你可以暂停线程太长时间,没有办法让线程重新开始而不等待上次停顿。 Rx提供了一种解决此问题的简便方法。试试这段代码:

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();

答案 1 :(得分:1)

只需使用全局变量,并将其标记为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;
    }

通常,你必须在这样的共享变量周围加一个互斥锁,但在大多数情况下都是integer updates are automatically atomic。如果您想要绝对安全,或者您打算移植到其他操作系统,或者如果您想使用long而不是int,那么您可以附上阅读和在lock块内更新,或使用Interlocked.Exchange,如下所示:

Interlocked.Exchange(ref WaitInterval, mySlider.Value);

答案 2 :(得分:-1)

John Wu的解决方案可行,但理想情况下,您不应该使用Thread.Sleep,尤其是在任务中,因为他们并不总是保证在单独的线程上运行。

相反,您可以使用TPL内置的延迟功能:

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;
}

正如斯维克指出的那样。在任务中使用async / await时,请使用:

&#39; Task.Run&#39;而不是&#39; Task.Factory.StartNew&#39;。

如果您遇到&#39; Task<Task<T>>&#39;但是,您可以使用&#39; .Unwrap()&#39;任务实例上的方法。