Dispatcher.Invoke пользовательский интерфейс блокировки цикла

У меня есть приложение для отслеживания журнала, которое выполняет фоновый рабочий поток в бесконечном цикле и проверяет последние записи в каком-то файле TXT. Как только он находит новые записи, я использую Dispatcher.Invoke для обновления TextBox на экране с последней записью, добавленной в текстовый файл.

Проблема в том, что если исходный текстовый файл постоянно обновляется каждую миллисекунду, пользовательский интерфейс просто зависает, так как Dispatcher.Invoke так часто обновляет Textbox.

Интересно, есть ли обходной путь. Я мог бы получить большие фрагменты из текстового файла, но не хочу портить работу журнала в реальном времени, а также не хочу откладывать запись строк в 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;        
  }
}

как вы можете видеть, поток пользовательского интерфейса просто добавляет текст в TextBox, и когда это случается, интерфейс часто зависает


person idelix    schedule 14.06.2012    source источник


Ответы (4)


Использование Dispatcher.Invoke — это синхронный вызов, поэтому он фактически аналогичен выполнению этого в потоке пользовательского интерфейса, поскольку вызов Invoke блокируется до тех пор, пока пользовательский интерфейс не выполнит запрошенную задачу. Если вы делаете это слишком много за короткий промежуток времени, вы фактически блокируете поток пользовательского интерфейса.

Вместо этого вы должны использовать Dispatcher.BeginInvoke, который ставит в очередь работу для потока пользовательского интерфейса, но не блокирует. Это будет немного лучше, так как если вы делаете это слишком много за короткий промежуток времени, вы все равно перегружаете поток пользовательского интерфейса работой, которую необходимо выполнить, и он потратит много времени на выполнение этой работы.

Вместо этого наилучшим подходом было бы поставить в очередь эти изменения в потоке пользовательского интерфейса, а затем, когда очередь достигнет определенного предела (т.е. 100 новых строк текста) или превысит определенное количество времени (скажем, 200 мс), затем вызовите Dispatcher.BeginInvoke для отправить эти изменения в пользовательский интерфейс. Это даст вам лучшую отзывчивость пользовательского интерфейса.

person CodingGorilla    schedule 14.06.2012

Вызывая Dispatcher.Invoke для каждой строки, которую вы читаете, вы эффективно заставляете каждую строку отправлять данные обратно в поток пользовательского интерфейса и ждать их завершения.

Это, скорее всего, сделает всю процедуру медленнее, чем простое использование потока пользовательского интерфейса напрямую, поскольку вы добавляете накладные расходы, но не переносите основную часть работы в фоновый поток.

Чтобы увеличить эту скорость, вам нужно сделать что-то, что буферизует данные и отправляет их большими порциями. В этом случае, поскольку вы ищете новые записи журнала, я бы рекомендовал сразу прочитать ВСЕ записи журнала в конце файла и упорядочить весь фрагмент обратно в ваш пользовательский интерфейс (вместо того, чтобы делать это построчно). ).

person Reed Copsey    schedule 14.06.2012

извините, но чтение файла в бесконечном цикле ошибочно и... ну... глупо. Существуют четко определенные классы, такие как FileSystemWatcher, нет необходимости нагружать ядро ​​ЦП на 100 % только потому, что вам нужны обновления вашего файла в реальном времени.

Я так понимаю, вы новичок в программировании - ну, мы все делаем ошибки и должны учиться, примите этот совет:

  • Бесконечные циклы автоматически являются ошибками программирования
  • Если циклы действительно необходимы, рекомендуется позволять соответствующему потоку засыпать между каждой итерацией.
person name    schedule 13.01.2013

Я согласен с ответом CodingGorilla.

Использование Dispatcher.Invoke приведет к синхронному вызову.

Это будет иметь тот же результат, что и вызов непосредственно в потоке пользовательского интерфейса.

Вызов будет заблокирован до тех пор, пока пользовательский интерфейс не выполнит запрошенную задачу, и если это произойдет в быстрой последовательности, ваш поток пользовательского интерфейса будет заблокирован, и, следовательно, вы не получите обновлений.

Попробуйте заменить Dispatcher.Invioke на Dispatcher.BeginInvoke и посмотрите, решит ли это вашу проблему.

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

}));

Также, как было предложено, вы можете захотеть перевести цикл в спящий режим на период 1 миллисекунды; используя Thread.Sleep(1); время от времени, чтобы не перегружать процессор.

person Richard Vella    schedule 18.12.2013