C# 睡眠任务(System.Threading.Tasks)

C# 睡眠任务(System.Threading.Tasks),c#,winforms,multithreading,sleep,taskfactory,C#,Winforms,Multithreading,Sleep,Taskfactory,我需要创建一个线程,它将替换窗口中的照片,然后等待~1秒并恢复上一张照片 我认为以下代码: TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext(); var task = Task.Factory.StartNew(() => { pic.Image = Properties.Resources.NEXT; Thread.Sleep(1000); pic.Image = Propert

我需要创建一个线程,它将替换窗口中的照片,然后等待~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线程

这是因为不能保证每个任务有一个线程。一个线程可用于处理多个任务。 即使选择也无济于事


如何修复它?

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


如果您真的想使用任务,您可能希望另一个任务不在UI线程中运行,而是在后台威胁中运行(即,只是一个常规的
StartNew
任务),然后使用控件。在任务内部调用以在UI线程上运行命令。这里的问题是“创可贴”消除了开始一项任务只是为了让它睡觉的潜在问题。最好不要让代码在第一位执行整整一秒钟。

当您传递来自当前同步上下文的新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在自动为您平衡性能方面做得很好

现在,如您所见,您不必使用默认的
任务调度器
。当您传递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线程上调用此线程,在更新映像时将其阻塞很短的时间。

thread.Sleep是一个同步延迟。如果需要异步延迟,请使用

在C#5中,目前正在测试版中,您可以简单地说

await Task.Delay(whatever);
在异步方法中,该方法将自动拾取它停止的位置


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

您可以使用
FromCurrentSynchronizationContext()
创建任务计划程序,对于WinForms,它将是UI线程。因此,您的任务最终在UI线程上运行,然后继续将其置于睡眠状态。不要让UI线程处于睡眠状态。您的代码直接更新UI,因此它必须在UI线程上运行。您的代码处于休眠状态,因此无法在UI线程上运行。结论:您的代码已被破坏。通过删除两个相互冲突的要求中的一个来修复它。我不使用计时器,因为事实上我有一个大的
TableLayoutPanel
,里面有
PictureBox
es(我不想有
PictureBox
es那样多的计时器)。我将此任务的代码放在OnClick事件处理程序中,因为我想将图片交换为1秒。点击后点击图片。你的代码工作得很好,但我不明白为什么。实现解决方案的关键是使用不同的
任务调度器继续执行任务(
.Default
one)。这是什么意思?将上下文更改为线程池线程上下文?那么我们停止剩下的线程?这对我来说还不清楚。@patryk.beza-也许我的编辑会让你更清楚。谢谢,我知道了。但还有一个问题:是否有任何方法可以在不中断UI线程的情况下从工作线程更新UI?在这个特定的问题中,这不是一个问题(:]),因为我们假设
pic.Image=Properties.Resources.XYZ
是一个快速的操作(而且速度足够快)。当然,您可以,但是您调用的代码仍然必须在UI线程上运行,所以您希望将其保持在执行实际调用的代码的最小数量。根据我的经验,任务看起来更干净,尽管两种方法都可以。@JosephLennox C#5==.NET 4.5