在任务中动态暂停线程
Dynamically Pause a Thread while in a Task
我的任务是添加一个滑块控件,其值是加快或减慢生成值的任务中的输出。我目前的任务是 运行 一个无限的 while 循环,并将 属性 设置回 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)
就用一个全局变量,标记一下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 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,像这样:
Interlocked.Exchange(ref WaitInterval, mySlider.Value);
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;
}
正如 Svick 指出的那样。在任务中使用async/await时,使用:
'Task.Run' 而不是 'Task.Factory.StartNew'.
如果您遇到“Task<Task<T>>
”,您可以在任务实例上使用“.Unwrap()”方法。
最好避免使用任务并使用 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();
来停止 observable。
您甚至应该能够在 .Subscribe
方法中使用值 x
来计算滑块的值。在此代码中,值以 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();
我的任务是添加一个滑块控件,其值是加快或减慢生成值的任务中的输出。我目前的任务是 运行 一个无限的 while 循环,并将 属性 设置回 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)
就用一个全局变量,标记一下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 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,像这样:
Interlocked.Exchange(ref WaitInterval, mySlider.Value);
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;
}
正如 Svick 指出的那样。在任务中使用async/await时,使用:
'Task.Run' 而不是 'Task.Factory.StartNew'.
如果您遇到“Task<Task<T>>
”,您可以在任务实例上使用“.Unwrap()”方法。
最好避免使用任务并使用 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();
来停止 observable。
您甚至应该能够在 .Subscribe
方法中使用值 x
来计算滑块的值。在此代码中,值以 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();