【问题标题】:Dispatcher.Invoke loop freeze UIDispatcher.Invoke 循环冻结 UI
【发布时间】:2013-12-18 08:39:51
【问题描述】:

我有日志跟踪应用程序,它在不定式循环中执行后台工作线程并检查某个 TXT 文件中的最新条目。找到新条目后,我使用 Dispatcher.Invoke 更新屏幕上的 TextBox,并将最新条目添加到文本文件中。

问题在于,如果源文本文件每毫秒不断更新,那么用户界面就会冻结,因为 Dispatcher.Invoke 经常更新 Textbox。

想知道是否有任何解决方法。我可以从文本文件中获得更大的块,但不想破坏日志跟踪实时体验,也不想延迟通过 UI 线程向 TextBox 写入行,因为这会使我的应用程序与内部的实际数据不同步源文本文件。

这里是后台工作者 DoWork 方法

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  reader = new StreamReader(new FileStream(file.File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
  lastMaxOffset = reader.BaseStream.Length;

  while (true)
  {
    if (worker.CancellationPending)
    {
      e.Cancel = true;
      break;
    }                

    if (reader.BaseStream.Length == lastMaxOffset)
      continue;

    reader.BaseStream.Seek(lastMaxOffset, SeekOrigin.Begin);

    string line = "";
    while ((line = reader.ReadLine()) != null)

    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
    {
      textLog.Text += "\r" + line;
      scrollViewer.ScrollToBottom();
    })); 

    lastMaxOffset = reader.BaseStream.Position;        
  }
}

如你所见,UI 线程只是简单地将文本附加到 TextBox,而当它碰巧经常出现界面冻结时

【问题讨论】:

    标签: wpf textbox backgroundworker dispatcher


    【解决方案1】:

    使用Dispatcher.Invoke 是一个同步调用,因此它实际上与在UI 线程上执行此操作相同,因为对Invoke 的调用会阻塞,直到UI 执行请求的任务。如果你在短时间内做的太多,你实际上是在阻塞 UI 线程。

    相反,您应该使用Dispatcher.BeginInvoke,它将工作排队等待 UI 线程执行,但不会阻塞。这会稍微好一些,就好像你在短时间内做的太多了,你仍然在 UI 线程中充斥着工作来做它会花费很多时间来做那个工作。

    相反,最好的方法是将这些更改排队到 UI 线程,然后当队列达到定义的限制(即 100 行新文本)或超过特定时间量(例如 200 毫秒)时调用 Dispatcher.BeginInvoke 将这些更改发送到 UI。这将为您提供最佳的 UI 响应能力。

    【讨论】:

      【解决方案2】:

      通过为您读取的每一行调用Dispatcher.Invoke,您实际上是在使每一行将数据推送回 UI 线程,并等待它完成。

      这可能会使整个例程比直接使用 UI 线程更慢,因为您会增加开销,但不会将大部分工作拉入后台线程。

      为了加快速度,您需要做一些事情来缓冲数据,并以更大的块发送数据。在这种情况下,由于您正在寻找新的日志条目,我建议您一次读取文件末尾的所有日志条目,并将整个块编组回您的 UI(而不是逐行执行) )。

      【讨论】:

        【解决方案3】:

        抱歉,在无限循环中读取文件既错误又……嗯……愚蠢。 有像 FileSystemWatcher 这样定义明确的类,没有必要仅仅因为您想要实时更新文件而将 100% 的负载放在您的 CPU 内核上。

        我认为你对编程很陌生 - 好吧,我们都会犯错误,必须学习,接受这个建议:

        • 无休止的循环是自动编程错误
        • 如果确实需要循环,建议在每次迭代之间让相应的线程休眠

        【讨论】:

          【解决方案4】:

          我同意 CodingGorilla 的回答。

          使用 Dispatcher.Invoke 将导致同步调用。

          这与直接在 UI 线程上调用的结果相同。

          调用将阻塞,直到 UI 执行请求的任务,如果这种情况快速连续发生,您的 UI 线程将阻塞,因此您将无法获得更新。

          尝试用 Dispatcher.BeginInvoke 替换您的 Dispatcher.Invioke,看看这是否能解决您的问题。

          // Load in background
          this.Dispatcher.BeginInvoke(new Action(() =>
          {
              textLog.Text += "\r" + line;
              scrollViewer.ScrollToBottom();
          
          }));
          

          同样,您可能希望循环休眠 1 毫秒;使用 Thread.Sleep(1);每隔一段时间,这样你就不会最终占用cpu。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-06
            • 2023-04-11
            • 2014-09-04
            • 2014-10-02
            • 2017-10-20
            • 1970-01-01
            相关资源
            最近更新 更多