Выполнить задачу асинхронного вызова / ожидания в BackgroundWorker's RunWorkerCompleted - PullRequest
0 голосов
/ 14 апреля 2020

У меня есть BackgroundWorker, который запускает задание, генерирующее большой объем текста.

Когда оно будет завершено, мне нужно, чтобы он выполнил метод Async/Await Task, который пишет и раскрашивает текст в RichTextBox.

Async/Await Task предотвращает зависание потока пользовательского интерфейса MainWindow во время вычисления работы, такой как поиск и раскрашивание, для RichTextBox.


Ошибка

Исключение: «Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку.»

Я получаю эту ошибку, если я не помещаю код Async/Await в Dispatcher.Invoke.

Но использование Dispatcher.Invoke, по-видимому, сводит на нет Async/Await и приводит к зависанию потока пользовательского интерфейса MainWindow.


C#

 public void Generate() 
 {
    // Background Worker
    //
    BackgroundWorker bw = new BackgroundWorker();
    bw.WorkerSupportsCancellation = true;
    bw.WorkerReportsProgress = true;

    bw.DoWork += new DoWorkEventHandler(delegate (object o, DoWorkEventArgs args)
    {
        BackgroundWorker b = o as BackgroundWorker;

        // Generate some text
        // ...
    });

    // When Background Worker Completes Job
    //
    bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate (object o, RunWorkerCompletedEventArgs args)
    {
        // Write and Colorize text in RichTextBox
        Task<int> task = Display();

        bw.CancelAsync();
        bw.Dispose();
    });

    bw.RunWorkerAsync();
}


// Method that Writes and Colorizes text in RichTextBox in MainWindow UI
// 
public async Task<int> Display()
{
    int count = 0;
    await Task.Run(() =>
    {
        // Problem here, it will only work inside a Disptacher
        //Dispatcher.Invoke(new Action(delegate {
            // Write text
            Paragraph paragraph = new Paragraph();
            richTextBox1.Document = new FlowDocument(paragraph);
            richTextBox1.BeginChange();
            paragraph.Inlines.Add(new Run("Test"));
            richTextBox1.EndChange();

            // Colorize Text here...
            // Is a loop that takes some time.
            // MainWindow UI freezes until it's complete.
        //}));
    });

    return count;
}

1 Ответ

4 голосов
/ 15 апреля 2020

Я согласен с другими, что код станет чище, если вы замените BackgroundWorker на Task.Run. Между прочим, гораздо проще сочинять с await (под «сочинять» я подразумеваю «сделай это; потом сделай это»). Обратите внимание, что вы должны использовать await вместо ContinueWith.

Таким образом, ваш исходный код будет выглядеть примерно так, как только BGW преобразуется в Task.Run:

public string GenerateSomeText(CancellationToken token)
{
  // Generate some text
}

public async Task GenerateAsync() 
{
  var cts = new CancellationTokenSource();
  var result = await Task.Run(() => GenerateSomeText(cts.Token));
  await DisplayAsync(result);
}

Итак, что касается вопроса, который вызвал этот вопрос, во-первых: как выполнять большую часть пользовательского интерфейса, не блокируя его? Ну, нет хорошего решения, потому что работа - Работа интерфейса . Так что это не может быть помещено в фоновый поток. Если у вас есть тонны работы пользовательского интерфейса, единственные реальные варианты:

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

Я не верю, что WPF RichTextBox поддерживает виртуализацию, поэтому для этого вам может потребоваться go сторонняя организация. Если вы хотите взломать паузу, вы можете сделать что-то вроде этого:

public async Task<int> DisplayAsync(string text)
{
  int count = 0;

  // Write text
  Paragraph paragraph = new Paragraph();
  richTextBox1.Document = new FlowDocument(paragraph);
  richTextBox1.BeginChange();
  paragraph.Inlines.Add(new Run(text));
  richTextBox1.EndChange();

  // Colorize Text here...
  // Is a loop that takes some time.
  for (loop)
  {
    ... // Colorize piece of text.
    await Task.Delay(TimeSpan.FromMilliseconds(20));
  }

  return count;
}
...