睡眠任务(System.Threading.Tasks)

时间:2012-04-10 22:54:43

标签: c# winforms multithreading sleep taskfactory

我需要创建一个替换Windows Forms窗口中的照片的线程,而不是等待 ~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.LongRunning选项也无济于事。

我该如何解决?

3 个答案:

答案 0 :(得分:25)

Thread.Sleep是同步延迟。如果您想要异步延迟,请使用Task.Delay

在目前处于测试阶段的C#5中,您可以简单地说

await Task.Delay(whatever);

在异步方法中,该方法将自动从中断处继续。

如果您没有使用C#5,那么您可以“手动”设置您希望自己延迟延迟的任何代码。

答案 1 :(得分:7)

当您传递来自当前同步上下文的新TaskScheduler时,您实际上告诉该任务在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 线程上运行的任务,该任务将使用您的资源更新Image pic属性。虽然这样做, 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线程上调用此线程,在图像更新时将其阻塞很短的时间。

答案 2 :(得分:5)

您应该在将来某个时候使用Timer执行UI任务。只需将其设置为运行一次,间隔为1秒。将UI代码放入tick事件中,然后将其关闭。

如果真的想要使用任务,那么您希望其他任务不在UI线程中运行,而是在后台威胁中运行(即只是常规StartNew任务)然后使用任务内部的Control.Invoke在UI线程上运行命令。这里的问题是“创造一个任务只是让它睡觉的基础问题”。最好只让代码在第一时间内不执行整秒。