【问题标题】:How to Make Sure UI is Responsive Using BackgroundWorker如何使用 BackgroundWorker 确保 UI 具有响应性
【发布时间】:2015-04-28 09:28:00
【问题描述】:

c# 线程中的 BackgroundWorker 是否安全?

我问这个的原因是因为我得到了一个

在一个线程上创建的控件不能 成为一个控件的父级 不同的线程

例外。这是我的DoWork 事件代码:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    var openFile = document.Open(MyFileName);
    e.Result = openFile;
}

其中document 是在创建父窗体时初始化的UI 控件。在Open 方法期间,document 中的各种属性将被填充。

我尝试更改要调用的代码,但同样的问题仍然存在。即,

document.GetType().GetMethod("Open)".Invoke(document, new object[]{MyFileName})

将产生与上述相同的错误。

知道如何操作document 控件吗?也就是说,如何让上面的代码工作?

编辑:有人建议我使用Control.Invoke,但它仍然不起作用(两个线程都挂了)。这是我试过的代码:

private delegate bool OpenFile(string filePath);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    OpenFile oF = new OpenFile(document.Open);
    var openFile = Invoke(oF, MyFileName);  // it doesn't really matter whether I use BeginInvoke or Invoke, or other Control.Invoke, the end result is the same. Both the main thread hosting the document and the thread that launches the UI hanged.

    e.Result = openFile;
}

【问题讨论】:

    标签: c# winforms backgroundworker


    【解决方案1】:

    问题不在于线程,而在于它试图调用 UI 控件上的方法。在 WPF 和 WinForms 中,控件只能在 UI 线程(通常有一个)上调用。您没有说您使用的是哪个,但您需要调用 WinForms 的 Control.Invoke 方法或 WPF 的 Dispatcher.Invoke

    你展示的Invoke()反射方法实际上会调用当前线程上的方法。

    【讨论】:

    • 我在 DoWork 活动中尝试了这段代码,但仍然无法正常工作:this.Invoke(document.Open)
    • 你还是调用了错误的方法。如果document 是Windows 窗体控件,那么您将调用document.Invoke(MyMethod),其中MyMethod 是调用document.Open 的委托。根据签名,您可以直接致电document.Invoke(document.Open)
    • @GraemeF,如果document.Invoke 不可用怎么办?
    • 那就不是控件了。你能告诉我们document是什么吗?
    • 好的,所以如果这不是一个控件,那么您可以在另一个控件或窗口上调用 Invoke,它会产生相同的效果;该方法将在 UI 线程上调用。换句话说,您需要与 UI 相关的一些东西才能进行调用。
    【解决方案2】:

    您可以按照 Mehrdad Afshari 的建议调用,也可以使用返回到 UI 线程的 bgw 进度事件。或者在 UI 线程上返回的工作完成事件。两者之间的区别是 WorkCompleted 在最后只触发一次。进度是由 DoWork 激发的。

    【讨论】:

    • “利用返回到 UI 线程的 bgw 的进度事件”如何解决我的问题,即能够拥有响应式 UI?
    • 也许这是对 BGW 为你做了什么的误解。如果您正在对 UI 控件进行大量操作,因此这很慢,并导致您的 UI 拉白或看起来冻结,bgw 无法帮助您!但是,如果您正在做其他事情(除了触摸 ui 控件),例如访问缓慢的 web 服务或数据库,那么在 bgw 上执行此操作将使您的 ui 保持响应。例如,您可以执行密集操作 x,然后使用进度事件向您的 UI 报告操作 x 已完成,这是中间结果,接下来执行操作 y
    【解决方案3】:

    虽然我不清楚BackgroundWorker 的线程安全到底是什么意思,但问题不在于那个对象; Windows 窗体控件设计为在单个线程(UI 线程)上进行操作。您不应在不同线程上操作 Windows 窗体对象。您可以使用Control.Invoke方法从其他线程调用UI线程中的操作(您当前使用的Invoke方法是通过反射提供的,与此问题完全无关):

    Invoke(new Action(MethodToRunInUIThread));
    
    void MethodToRunInUIThread() {
        // do stuff here.
    }
    

    顺便说一句,如果您所做的只是操作 UI 对象,那么使用后台工作器是没有意义的。

    【讨论】:

    • @Mehdrad,我需要一个后台工作人员来保持 UI 响应,这它有意义的原因。
    • @Ngu Soon Hui:当然,如果你有一个长时间运行的任务,而不仅仅是 UI 操作,你会使用它。如果您在DoWork 方法中所做的all 是为了操作UI 对象,那么您不应该使用BackgroundWorker,原因是您最终必须在UI 线程。
    • 这很有意义,而且可能更微妙。该线程可能只是通知“UI 线程”“发生了新的事情”,然后由 UI 必须在需要时更新 UI 的通知处理程序来决定。
    • @Johann:我知道。在那种情况下,if all you're doing is UI operations 将是错误的,你知道的。我这么说是因为显然 OP 发布的代码只是在控件上调用一个方法,应该使用 InvokeInvoke 来完成只是在 UI 线程上运行该方法,所以使用 BackgroundWorker 是没有意义的在这里。
    【解决方案4】:

    如果 UI 控件的该功能需要很长时间才能执行,那么您可能无能为力。当 UI 线程上发生长时间运行的操作时,会发生“冻结”,如果控件的该功能没有专门设置为线程安全的,则它必须在主线程上运行。

    通常,您希望将“文档”功能与显示它的控件分开。这样,您的文档可以加载到单独的独立线程上,并在准备好后显示。否则,控件本身将不得不实现一个多线程加载例程来减缓加载冻结。

    由于您已在您的 cmets 中指定这是第三方控件,因此您可能在此处不走运。

    【讨论】:

      【解决方案5】:

      BackgroundWorker 是一个基于线程的结构。线程安全问题与执行同时任务时的功能有关。也许您要求的是有关通过唯一线程(即用户界面线程)访问的 winforms 控件。

      【讨论】:

        【解决方案6】:

        您需要在 DoWork 中使用Control.BeginInvoke()。这会异步执行委托,因此将确保调用线程不会“挂起”。

        Control.Invoke() 也会在另一个线程上执行委托,但会导致调用线程等待它完成。

        通常在 Windows 窗体中,您最好尽可能使用 Control.BeginInvoke() 来帮助避免在一个线程等待另一个线程时可能发生的线程之间的死锁,就像 Control.Invoke() 一样。

        如果“文档”对象继承自 System.Windows.Forms.Control,您可以简单地调用 document.BeginInvoke(myDelegate)。

        但是如果它实际上是封装了 GUI 控件的其他组件,它可能会暴露一些调用 BeginInvoke 的方法。检查文档(如果有)。如果没有这样的能力,那么不幸的是,它可能只是不支持多线程应用程序。

        您似乎对各种 Invoke/BeginInvoke 类型感到困惑(可以理解)。这个较早的问题:What is the difference between Invoke and BeginInvoke? 和 Jon Skeets 的回答应该有助于澄清事情。

        【讨论】:

        • 我明白,但是当我调用BeginInvoke 时,承载document 的线程和另一个启动移动对话框的线程挂起。
        【解决方案7】:

        @Graviton,找到了一个有答案的相关任务here。该人正在使用 BackgroundWorker 更新文本框,同样的概念适用(您的只是一个工作线程)。

        【讨论】:

        • 是的,确实如此,请参阅我更新的问题。我发现在应用链接中指定的技术后,两个线程都挂起。
        • 您似乎没有将 InvokeRequired 检查到位并如示例中所示进行 BeginInvoke() 调用。 InvokeRequired 确保您处于正确的线程中,并且使用 BeginInvoke() 的调用基本上是回调到当前方法。我知道该文档不提供 Invoke 方法,因此请使用表单对象(这可能是您在第二个代码示例中调用 Invoke() 时正在执行的操作)。请记住,调用 BeginInvoke() 是一回事……该方法需要继续调用 BeginInvoke(),直到 InvokeRequired 为 false。
        猜你喜欢
        • 2022-12-11
        • 2022-06-27
        • 1970-01-01
        • 1970-01-01
        • 2017-12-30
        • 2018-08-01
        • 1970-01-01
        • 2020-09-15
        • 2016-12-18
        相关资源
        最近更新 更多