关于 c#:将 GUI 值传递给后台工作人员的正确方法?

Proper way to pass GUI values to backgroundworker?

我正在使用一个相当复杂的 GUI,并试图将大量数据从 GUI 传递到 backgroudWorker。我遇到的问题是从后台工作人员访问一些 GUI 值。例如,如果我尝试获取 ComboBox.Text,由于跨线程,我会得到一个 InvalidOperationException。但是,如果我说做 TextBox.Text,一切似乎都正常。当然,我对 C# 还很陌生,所以我有点不清楚为什么其中一些可以,而另一些则失败。

我想出了几种方法来解决我的问题,但我正在向有 c# 经验的人寻求最佳实践。

以下是我能想到的解决此问题的几种方法

  • 创建所有要传递给后台工作人员的值的类/结构,并在调用 RunworkAsync 时传递它。我没有发现这很有吸引力,因为我必须为我的 GUI 上的每个页面构建一个类/结构以传递给 backgroundWorker

  • 创建一组具有特定任务的不同后台工作人员。我在传递数据方面仍然存在一些问题,但是我必须传递的数据量被减少了很多。但是,DoWork/ProgressChanged/RunworkerCompleted 的数量显着增加,并不理想。

  • (这引导我了解我目前正在做的事情)

  • 创建一个委托和方法来捕获信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private delegate string ReadComboDelegate(ComboBox c);

    private string ReadComboBox(ComboBox c)
    {
        if(c.InvokeRequired)
        {
            ReadComboDelegate del = new ReadComboDelegate(this.ReadComboBox);
            return (string) c.Invoke(del,c);
        }
        else
        {
            return c.Text
        }
    }

    然后在 DoWork 中,执行类似 string txt = this.ReadComboBox(this.comboBox1);

    的操作

    当您有一个简单的 GUI 并且您不必传递大量数据时,这是一个非常简单的问题。然而,GUI 项目越多、越复杂,这个问题就越大。如果有人有任何信息可以使这更容易,我将不胜感激。

    谢谢


    您遇到的跨线程问题是由于要求仅允许 UI 线程"触摸"UI 控件。

    我认为将数据传递给后台工作人员的最一致的方法是您的解决方案 #1 - 创建一个包含执行处理所需的所有数据的简单结构。

    这比为 UI 中的每个控件创建 ReadXXX 方法要简单得多,它定义了后台进程执行其任务所需的内容...


    TextBox 不会导致此异常是很偶然的。它的 Text 属性缓存在一个字符串中。对于 ComboBox.Text 和绝大多数其他控件属性而言,情况并非如此,它会询问本机 Windows 控件,此时 Windows 窗体会发现您正在尝试使用来自 UI 线程以外的线程的控件。没办法。

    您肯定需要想办法重构这段代码。这不仅是非法的,而且非常昂贵并且从根本上线程不安全,因为 UI 可以在您的工作人员运行时更新。从您需要的控件中收集信息到一个小助手类中,将其作为参数传递给 RunWorkerAsync(object) 重载。并从 e.Argument.

    将其取回 DoWork


    我肯定会避免#3。尽管人们热衷于使用 Control.Invoke 来协调工作线程和 UI 线程,但它经常被过度使用并且通常充其量是次优策略。我更喜欢#1和#2而不是#3。以下是我倾向于避免使用 #3 的原因。

    • 它将 UI 和工作线程紧密耦合在一起。
    • 工作线程可以决定 UI 线程执行多少工作。
    • 工作线程必须等待 UI 线程响应才能继续。
    • 这是一项昂贵的手术。

    我知道您可能需要做一些额外的前期工作才能让 #1 或 #2 顺利进行,但从长远来看,最终结果会更好。

    作为我回答的必然结果,当数据也需要遵循相反的方向(从工作线程到 UI 线程,例如向 UI 发送进度信息的情况下)时,Control.Invoke 方法往往会被过度使用。遗憾的是,这是 BackgroundWorker 内部使用的方法及其 ReportProgress 方法。通常最好让 UI 线程轮询共享数据结构以获取此信息,原因与上述相同,另外还有:

    • UI 线程可以决定更新的时间和频率。
    • 它将更新 UI 线程的责任放在它应该属于的 UI 线程上。
    • 不存在 UI 消息泵溢出的风险,就像工作线程启动的编组技术那样。

    但是,话虽如此,我并不是建议您完全放弃 BackgroundWorker。请记住其中一些要点。