在后台运行方法和UI线程WPF

本文关键字:UI 线程 WPF 方法 后台 运行 | 更新日期: 2023-09-27 18:26:24

我遇到以下示例的问题:

public void Method()
{
  LongRunningMethod();
}

LongRunningMethod()调用大约需要5秒。我从UI线程调用Method(),所以它显然应该冻结UI。解决方案是在新的Task中运行Method(),所以我这样运行它:

Task.Factory.StartNew(()=>{Method()})

它仍然在阻塞UI,所以我想LongRunningMethod()是否可能在使用UI上下文。然后我尝试了另一种解决方案:

new Thread(()=>Method()).Start()

它开始工作了。这怎么可能?我知道Task不能保证在不同的线程上运行,但CLR应该足够聪明,能够发现它是一个长时间运行的方法。

在后台运行方法和UI线程WPF

您正在用户界面(UI)线程上安排工作,因为您正在使用此代码中的TaskScheduler.FromCurrentSynchronizationContext())

Task nextTask = task.ContinueWith(x =>
   {
       DoSomething();
   }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
       task.Start();
     }

这就是为什么你的用户界面被冻结的原因。为防止尝试将TaskScheduler更改为Default:

Task task = Task.Run(() => {; });
Task nextTask = task.ContinueWith(x =>
{
   //DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);

Task.Factory.StartNew是危险的,因为它使用TaskScheduler.Current而不是TaskScheduler.Default。为了防止这种情况,请使用始终指向TaskScheduler.DefaultTask.RunTask.Run在.NET 4.5中是新的,如果您在.NET 4.0中,则可以使用默认参数创建TaskFactory

正如MSDN所说:

TaskScheduler.FromCurrentSynchronizationContext())表示时间表用户界面(UI)控件所在线程上的任务创建于.

更新:

运行方法RunTask()时会发生什么:

  1. var task = new Task(action, cancellationTokenSource.Token);

    创建一个"任务"。(任务未运行。"任务"刚刚被发送到ThreadPool。)

  2. Task nextTask = task.ContinueWith(x => { DoSomething(); }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

创建一个"nextTask",它将在"task"完成后开始执行,并且当您设置了一个功能时,"nextTask"将在UI线程上执行CCD_ 22。

  1. task.Start();

你运行你的"任务"。当"任务"完成时,"nextTask"将通过方法"task.ContinuWith()"运行,该方法将在您编写(TaskScheduler.FromCurrentSynchronizationContext() 的UI线程上执行

总之,这两个任务是相互关联的,并且task的延续是在UI线程上执行的,这也是冻结UI的原因。要防止这种行为,请使用TaskScheduler.Default

这正是它的样子:

 public void RunTask(Action action){
   var task = new Task(action, cancellationTokenSource.Token);
 Task nextTask = task.ContinueWith(x =>
   {
       DoSomething();
   }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
       task.Start();
     }
public void DoSomething()
{
    if(condition) // condition is true in this case (it's recurency but not permanent)
    RunTask(() => Method()); // method is being passed which blocks UI when invoked in RunTask method
}
public void Method()
{
  LongRunningMethod();
}

这是起点调用(UI线程):

  RunTask(()=>Action());

只是猜测:Thread.Start创建了一个前台线程。当该方法检测到它是从后台线程运行时,它可能会切换到已知的前台线程。

希望它能有所帮助。