睡眠任务(System.Threading.Tasks)
本文关键字:Threading Tasks System 任务 | 更新日期: 2023-09-27 18:30:52
我需要创建线程来替换Windows窗体窗口中的照片,而不是等待~1秒并恢复上一张照片。
我认为以下代码:
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
Thread.Sleep(1000);
pic.Image = Properties.Resources.PREV;
}, CancellationToken.None, TaskCreationOptions.LongRunning, ui)
做这项工作,但不幸的是没有。它冻结主 UI 线程。
这是因为不能保证每个任务有一个线程。一个线程可用于处理多个任务。甚至 TaskCreationOptions.LongRun 选项也无济于事。
我该如何解决?
Thread.Sleep 是一个同步延迟。如果需要异步延迟,请使用 Task.Delay。
在目前处于测试版的 C# 5 中,您可以简单地说
await Task.Delay(whatever);
在异步方法中,该方法将自动从中断的位置继续。
如果您没有使用 C# 5,那么您可以"手动"设置您想要的任何代码作为延迟的延续。
当您传递来自当前同步上下文的新任务计划程序时,您实际上告诉该任务在 UI 线程上运行。 您实际上想要这样做,因此您可以更新 UI 组件,但是您不想在该线程上睡觉,因为它会阻塞。
这是.ContinueWith
理想的一个很好的例子:
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
编辑(删除了一些东西并添加了这个):
发生的情况是,我们阻止 UI 线程的时间只够更新pic.Image
。 通过指定TaskScheduler
,您可以告诉它要在哪个线程上运行任务。 重要的是要知道任务和线程之间的关系不是 1:1。 事实上,你可以在相对较少的线程上运行 1000 个任务,甚至 10 个或更少,这完全取决于每个任务的工作量。 不要假设您创建的每个任务都将在单独的线程上运行。 CLR 在自动平衡性能方面做得很好。
现在,您不必使用 默认TaskScheduler
,如您所见。 当您传递 UI TaskScheduler
时,即TaskScheduler.FromCurrentSynchronizationContext()
,它使用 UI 线程而不是线程池,就像TaskScheduler.Default
一样。
记住这一点,让我们再次查看代码:
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
在这里,我们将创建并启动一个将在 UI 线程上运行的任务,该任务将使用资源更新 pic
的 Image
属性。 执行此操作时,UI 将无响应。 幸运的是,这可能是一个非常快速的操作,用户甚至不会注意到。
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
使用此代码,我们调用 ContinueWith
方法。 它完全符合它的声音。 它返回一个新的 Task
对象,该对象将在运行时执行 lambda 参数。 当任务完成、出错或被取消时,它将启动。 您可以通过传入 TaskContinuationOptions
来控制它何时运行。 但是,我们也像以前一样传递了不同的任务计划程序。 这是默认任务计划程序,它将在线程池线程上执行任务,因此不会阻止 UI。 此任务可能会运行数小时,并且您的 UI 将保持响应(不要让它),因为它是与您与之交互的 UI 线程不同的线程。
我们还对设置为在默认任务计划程序上运行的任务调用了ContinueWith
。 这是将再次更新 UI 线程上的图像的任务,因为我们已将相同的 UI 任务计划程序传递给正在执行的任务。 线程池任务完成后,它将在 UI 线程上调用此任务,在更新映像时将其阻止很短的时间。
您应该在将来的某个时候使用 Timer
来执行 UI 任务。 只需将其设置为运行一次,间隔为 1 秒即可。 将 UI 代码放在 tick 事件中,然后将其关闭。
如果您确实想使用任务,则希望另一个任务不在 UI 线程中运行,而是在后台威胁中运行(即只是一个常规StartNew
任务),然后在任务内部使用 Control.Invoke 在 UI 线程上运行命令。 这里的问题是,"创可贴"启动任务只是为了让它睡觉的根本问题。 最好只是让代码甚至不首先执行完整的第二个。