pat*_*eza 7 c# multithreading sleep winforms taskfactory
我需要创建一个将替换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)
Run Code Online (Sandbox Code Playgroud)
做的工作,但不幸的是没有.它冻结了主UI线程.
那是因为并不能保证每个任务都有一个线程.一个线程可用于处理多个任务.即使TaskCreationOptions.LongRunning选项也无济于事.
我怎么解决它?
Eri*_*ert 25
Thread.Sleep是一个同步延迟.如果您想要异步延迟,请使用Task.Delay.
在目前处于测试阶段的C#5中,您可以简单地说
await Task.Delay(whatever);
Run Code Online (Sandbox Code Playgroud)
在异步方法中,该方法将自动从中断的地方继续.
如果你没有使用C#5那么你可以"手动"设置你想要的任何代码自己延迟延迟.
当您传递来自当前同步上下文的新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);
Run Code Online (Sandbox Code Playgroud)
编辑(删除了一些东西并添加了这个):
会发生什么是我们阻止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);
Run Code Online (Sandbox Code Playgroud)
在这里,我们创建并启动将在UI线程上运行的任务,该任务将使用您的资源更新Image属性pic.虽然它这样做,但UI将没有响应.幸运的是,这可能是一个非常快速的操作,用户甚至不会注意到.
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
Run Code Online (Sandbox Code Playgroud)
使用此代码,我们将调用该ContinueWith方法.它确实听起来像它.它返回一个新Task对象,该对象在运行时将执行lambda参数.当任务完成,出现故障或被取消时,它将启动.您可以通过传入来控制它何时运行TaskContinuationOptions.但是,我们也像之前一样传递了不同的任务调度程序.这是将在线程池线程上执行任务的默认任务调度程序,因此不会阻止UI.此任务可能会运行数小时,您的UI将保持响应(不要让它),因为它是与您正在交互的UI线程的单独线程.
我们还调用ContinueWith了我们设置为在默认任务调度程序上运行的任务.这是将再次更新UI线程上的图像的任务,因为我们已将相同的UI任务调度程序传递给执行任务.线程池任务完成后,它将在UI线程上调用此线程,在图像更新时将其阻塞很短的时间.
您应该Timer在将来的某个时候使用a 来执行UI任务.只需将其设置为运行一次,间隔为1秒.将UI代码放入tick事件中,然后将其设置为off.
如果你真的想要使用任务,你需要让其他任务不在UI线程中运行,而是在后台威胁(即只是常规StartNew任务)中运行,然后使用任务内部的Control.Invoke来运行UI线程上的命令.这里的问题是"创造一个任务只是让它睡觉的基础问题".最好让代码甚至不在第一个位置执行整秒.