【问题标题】:How can I fit my code into a Background Worker?如何将我的代码放入后台工作人员?
【发布时间】:2015-04-27 14:42:57
【问题描述】:

我已经构建了一个执行以下操作的表单:

  1. 抓取目录中的所有文件路径(文件采用指定的命名约定)
  2. 使用分隔符解析文件路径
  3. 以指定格式输出文本文件。

问题是某些目录包含数百个演出,这会冻结表单 UI,直到该过程完成。我已经阅读了有关后台工作人员的信息,据我了解,您的代码中的数据密集型部分属于 DoWork 方法。但是,当我尝试在 DoWork 方法中实现我的代码时,我遇到了“跨线程操作无效:”错误。我查找了错误,但我根本不明白如何在不重新修改程序的整个结构的情况下解决此错误。

我的代码非常冗长,但如果需要我可以提供。

【问题讨论】:

  • 简短的总结代码会有很大帮助。但有一个猜测:也许您正在使用 BackgroundWorker 之外的 GUI 做任何事情。这是导致此错误的典型原因。
  • 请提供代码示例以更准确地查明问题。
  • 当我单步执行代码时,它会在这个 if 语句中出错: if ((TB_path.TextLength > 0) && (TB_TrackingNum.Text != "") && (TB_OutputPath.Text != "" )) - 据我了解,这并没有对 GUI 做任何事情。

标签: c# multithreading backgroundworker


【解决方案1】:

最好的方法是重新编写代码,使其不使用 UI 组件。

完成后,您可以使用 ReportProgress/ProgressChanged 和 RunWorkerCompleted 刷新 UI。

如果你还想使用 UI 组件,你可以试试 Invoke :

this.Invoke(new Action(() => 
    {
        //code that uses UI for instance :
        //this.TextBox1.Text = "test"
    });

【讨论】:

    【解决方案2】:

    当您尝试从 BackGroundWorker 写入表单上的控件时,会发生错误。编写控件时需要添加 InVoke。请参阅以下网页:https://msdn.microsoft.com/en-us/library/ms171728%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

    【讨论】:

      【解决方案3】:

      最可能的罪魁祸首是您正在从后台工作人员访问控件所持有的值。除非调用返回 GUI 线程,否则不能从这些对象写入,也不能读取。长话短说,当我尝试从我的 do work 线程的文本框中读取值时遇到了同样的问题,即使它是读取,它也失败了。

      我建议您从后台工作人员中删除所有数据访问/更新以解决此问题。

      这是我写的关于后台工作者的文章

      C# WPF: Linq Fails in BackgroundWorker DoWork Event

      C# WPF: Threading, Control Updating, Status Bar and Cancel Operations Example All In One

      【讨论】:

      • 那么这意味着我必须重组我的大部分代码,因为我的程序正在读取多个文本框。任何解决方法?我知道它不推荐,但实施 Control.CheckForIllegalCrossThreadCalls = false; 的缺点是什么?并在进程运行时禁用所有控件?
      • @ErickAlfaro 我已经看到系统工作,然后在更改不相关的代码时失败。话虽如此,您可以让它与该支票一起使用,但在未来更新后仍处于同一条船上;这是你必须权衡的事情。我知道您的目标是不更改您的代码,但编写的代码已使 gui 的线程能力超载以正确更新屏幕。为了正确地做到这一点,必须分离 GUI 操作并向后台工作人员提供值(提取值以传递给工作线程);这是需要支付编码成本的。
      • @ErickAlfaro 在启动 worker 之前,将文本框中的值读取到保存的值中(在 VM 上?),后台进程可以通过特洛伊木马安全获取的方式读取这些值值进入工作线程。
      【解决方案4】:

      您的代码从创建它们的线程之外的线程访问 GUI 控件的属性。 WindowsForms/WPF 在内部对其进行检查,发现您正在从另一个线程访问控件的属性并引发异常。

      我建议您使用 MVVM 模式。此外,BackgroundWorker 现在被认为已过时,您应该使用带有 async/await 关键字的任务。以下是我在 WPF 应用程序中的操作方式:

      public class ViewModel : INotifyPropertyChanged
      {
          ...
      
          public string PathInfo { ... } // raises INotifyPropertyChanged.PropertyChanged event from the setter
          public RelayCommand ProcessPathsCommand { get; set; }
      
          public ViewModel()
          {
              ProcessPathsCommand = new RelayCommand(ProcessPaths);
          }
      
          public async void ProcessPaths()
          {
              // disable the command, which will lead to disabling a button bound to the command
              ProcessPathsCommand.IsEnabled = false;
      
              try
              {
                  // run processing on another thread
                  await Task.Run(() =>
                  {
                      // emulate hard-work
                      Thread.Sleep(5000);
                      // update the property on the view model, which will lead to updating a textblock bound to this property                
                      // in WPF you can update the bound property right from another thread and WPF will automatically dispatch PropertyChanged event to the main UI thread. In WinForms this would lead to an exception.
                      PathInfo = "Here are the results: bla bla bla";
                  });
                  // In WinForms you would need to update the bound property here:
                  // Thanks to Tasks and await this line of code will be executed only after the task is finished
                  // PathInfo = resultFromTaskThread;
              }
              finally
              {
                  ProcessPathsCommand.IsEnabled = true;
              }
          }
      

      我们中的许多人都记得,在 WindowsForms 中,我们必须使用 Control.Invoke() 从另一个线程更新 GUI 控件(而且我们中的许多人仍然在 WPF 中使用 Dispatcher.Invoke,即使它不是必需的)。有了 WPF,微软终于让我们变得更轻松了! WPF 自动将 PropertyChanged 事件分派到主 GUI 线程,所以我们可以简单地从另一个线程更新视图模型上的属性(绑定到 UI 上的某个控件),属性设置器将引发 PropertyChanged 事件,WPF 将自动分派此事件到UI 线程!
      你也可以在这里阅读:
      Multithreading and Dispatching in MVVM Applications

      如果您需要更多详细信息,请随时告诉我。

      【讨论】:

      • 不要从另一个线程更改虚拟机属性!等待任务后更新属性。
      • @Guillaume,这适用于 WindowsForms,但不适用于 WPF。请查看我的更新答案。
      • @nightcoder 在某些情况下,这也适用于 WPF。例如与集合。因此,我建议不要从不同的线程更改 VM 属性。在WorkerCompleted-事件中执行此操作。如果您想交流进度,请使用BackgroundWorker 中的适当方法。
      • @pyrocumulus,我从我的同事那里听说过这样的事情,他说他记得从不同线程更新绑定的 ObservableCollection 的一些问题。但我现在无法在快速测试中重现它们。也许你知道我如何重现这个问题?
      【解决方案5】:

      如果 TB_path 或 TB_TrackingNum 是 UI 元素(我怀疑它们是),那么您正在尝试访问 UI。

      如果您只是将 UI 元素绑定到公共属性,则可以从 BackgroundWorker 访问公共属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-08
        • 1970-01-01
        • 1970-01-01
        • 2014-04-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多