【问题标题】:Running multiple background tasks in a WPF applicatiion - UI update stalling在 WPF 应用程序中运行多个后台任务 - UI 更新停滞
【发布时间】:2020-02-21 09:37:19
【问题描述】:

我已经尝试了很多此代码的变体。无论如何,我都会收到同样的问题。 UI 更新开始正常,然后停止,直到整个过程完成。有人能指出我正确的方向吗?

场景

在 WPF 应用程序中,我们将通过传递不同的参数调用相同的 API 数千次。我们需要收集所有响应并做点什么。

示例代码

List<Task> tasks = new List<Task>();

for (int i = 1; i <= iterations; i++)
{
   Task t = SampleTask(new SampleTaskParameterCollection { TaskId = i, Locker = locker, MinSleep = minSleep, MaxSleep = maxSleep });
   tasks.Add(t);
}

Task.WhenAll(tasks);
private void SampleTask(SampleTaskParameterCollection parameters)
{
   int sleepTime = rnd.Next(parameters.MinSleep, parameters.MaxSleep);
   Thread.Sleep(sleepTime);

   Application.Current.Dispatcher.BeginInvoke(new Action(() =>
   {
      lock (parameters.Locker)
      {
         ProgressBar1.Value = ProgressBar1.Value + 1;
         LogTextbox.Text = LogTextbox.Text + Environment.NewLine + "Task " + parameters.TaskId + " slept for " + sleepTime + "ms and has now completed.";
      }
      LogTextbox.ScrollToEnd();

      if (ProgressBar1.Maximum == ProgressBar1.Value)
      {
         RunSlowButton.IsEnabled = true;
         RunFastButton.IsEnabled = true;
         ProgressBar1.Value = 0;
      }
   }), System.Windows.Threading.DispatcherPriority.Send);
}

当前回购位于GitHub。看看 SimpleWindow。

【问题讨论】:

  • 可能发生的情况是,将文本添加到文本框并将其滚动到末尾所需的时间比更新之间的时间长,这导致一切都变得停。您可能需要不那么频繁地更新 UI。另外,不要创建数以千计的任务!这肯定会破坏一切。
  • 此代码和您的 repo 中的代码未编译。您不能将 new 与方法一起使用
  • 您在此处发布的代码无法编译 - 如果SampleTask 是方法,则new SampleTask(...) 无法编译。
  • 你能在挂起期间暂停调试器并发布调用堆栈吗?这样我们就可以知道是什么阻塞了 UI 线程。顺便说一句,Task.WhenAll(tasks); 什么都不做。
  • new SampleTask(... 表示SampleTask 是一个类,private void SampleTask(... 表示它是一个方法。非常混乱。

标签: c# wpf multithreading


【解决方案1】:

不要创建数千个任务 - 这会导致巨大的性能问题。

改为使用Parallel.For() 之类的东西来限制同时运行的任务数量;例如:

Parallel.For(1,
    iterations + 1,
    (index) =>
    {
        SampleTask(new SampleTaskParameterCollection { TaskId = index, Locker = locker, MinSleep = minSleep, MaxSleep = maxSleep });
    });

此外,如果 UI 更新所用的时间超过调用 BeginInvoke() 之间的时间间隔,那么调用将开始排队,事情会变得很糟糕。

为了解决这个问题,您可以在 SampleTask() 中使用一个计数器,以便仅在每 N 次调用时才实际更新一次 UI(使用合适的 N 值)。

但是,请注意,为避免线程问题,您必须在递增和检查计数器值时使用Interlocked.Increment()(或其他锁)。您还必须确保在完成所有工作后最后一次更新 UI。

【讨论】:

  • 谢谢马修。我确实在另一次尝试中使用了 Parallel.For。我认为您简单地发送太多 UI 更新是正确的。我会解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多