首页 > 解决方案 > 在 BackgroundWorker 的 RunWorkerCompleted 中运行 Async/Await 任务

问题描述

我有一个BackgroundWorker运行生成大量文本的作业。

完成后,我需要它来执行一个Async/Await Task方法,该方法在RichTextBox.

Async/Await Task是为了防止MainWindowUI 线程在计算工作(例如搜索和着色)时冻结RichTextBox


错误

例外:“调用线程无法访问此对象,因为不同的线程拥有它。”

除非我将Async/Await代码放入Dispatcher.Invoke.

但是使用 aDispatcher.Invoke似乎会否定Async/Await并导致MainWindowUI 线程冻结。


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;
}

标签: c#multithreadingasynchronousasync-await

解决方案


我同意其他人的观点,即一旦您将代码替换BackgroundWorkerTask.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);
}

那么,首先提出这个问题的问题:如何在不阻塞 UI 的情况下做大量的 UI 工作?好吧,没有一个很好的解决方案,因为工作是UI 工作。所以它不能放在后台线程上。如果你有大量的 UI 工作要做,唯一真正的选择是:

  1. 虚拟化您的数据。这样,您只需要处理您正在显示的 UI 数量。这是解决此问题的最佳方法。
  2. 如果您不想投入工作来虚拟化您的数据,那么您可以投入一个 hack,您的代码会定期暂停 UI 工作,以便 UI 保持响应。

我不相信 WPFRichTextBox支持虚拟化,所以你可能需要去第三方。如果您想改用 pause hack,则可以执行以下操作:

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;
}

推荐阅读