【问题标题】:How do I Yield to the UI thread to update the UI while doing batch processing in a WinForm app?在 WinForm 应用程序中进行批处理时,如何让 UI 线程更新 UI?
【发布时间】:2008-09-19 22:06:50
【问题描述】:

我有一个用 .NET 3.5 用 C# 编写的 WinForms 应用程序。它运行一个冗长的批处理过程。我希望应用程序更新批处理正在执行的状态。更新 UI 的最佳方式是什么?

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    BackgroundWorker 听起来像你想要的对象。

    【讨论】:

      【解决方案2】:

      快速而肮脏的方法是使用Application.DoEvents() 但这可能会导致订单事件处理出现问题。所以不推荐

      问题可能不是您必须屈服于 ui 线程,而是您在 ui 线程上进行处理,阻止它处理消息。您可以使用 backgroundworker 组件在不同的线程上进行批处理,而不会阻塞 UI 线程。

      【讨论】:

        【解决方案3】:

        在后台线程上运行冗长的进程。后台工作程序类是一种简单的方法——它为发送进度更新和完成事件提供了简单的支持,在正确的线程上为您调用事件处理程序。这样可以保持代码简洁明了。

        要显示更新,进度条或状态栏文本是最常见的两种方法。

        要记住的关键是,如果您在后台线程上执行操作,则必须切换到 UI 线程才能更新窗口控件等。

        【讨论】:

          【解决方案4】:

          为了加强人们对 DoEvents 的看法,以下是对可能发生的情况的描述。

          假设您有一些包含数据的表单,并且您的长期运行事件正在将其保存到数据库或基于它生成报告。您开始保存或生成报告,然后定期调用 DoEvents 以使屏幕继续绘制。

          不幸的是,屏幕不仅仅是绘画,它还会对用户操作做出反应。这是因为 DoEvents 会停止您现在正在执行的操作,以处理所有等待由您的 Winforms 应用程序处理的 Windows 消息。这些消息包括重绘请求,以及任何用户输入、点击等。

          因此,例如,当您保存数据时,用户可以执行一些操作,例如让应用显示一个与长时间运行的任务完全无关的模式对话框(例如帮助->关于)。现在,您正在对已经运行的长时间运行的任务内部的新用户操作做出反应。 DoEvents 将在您调用它时等待的所有事件完成后返回,然后您的长时间运行的任务将继续。

          如果用户没有关闭模态对话框怎么办?您长时间运行的任务将永远等待,直到此对话框关闭。如果您正在提交一个数据库并持有一个事务,那么现在您在用户喝咖啡时持有一个打开的事务。要么您的事务超时并且您丢失了持久性工作,要么事务没有超时并且您可能会死锁数据库的其他用户。

          这里发生的事情是 Application.DoEvents 使您的代码可重入。参见维基百科定义here。请注意文章顶部的一些要点,即要使代码可重入,它:

          • 不得保存静态(或全局)非常量数据。
          • 必须只处理调用者提供给它的数据。
          • 不得依赖对单例资源的锁定。
          • 不得调用不可重入的计算机程序或例程。

          WinForms 应用程序中长时间运行的代码不太可能只处理调用者传递给方法的数据,不保存静态数据,不持有锁,并且只调用其他可重入方法。

          正如这里很多人所说,DoEvents 会导致代码中出现一些非常奇怪的场景。它可能导致的错误很难诊断,并且您的用户不太可能告诉您“哦,这可能是因为我在等待保存时单击了这个不相关的按钮”。

          【讨论】:

            【解决方案5】:

            使用Backgroundworker,并且如果您还尝试通过处理ProgressChanged 事件来更新GUI 线程(例如,对于ProgressBar),请务必同时设置WorkerReportsProgress=true,或者线程第一次尝试调用ReportProgress时,报告进度将终止...

            一个异常被抛出,但你可能看不到它,除非你启用了'when throwed',并且输出只会显示线程退出。

            【讨论】:

              【解决方案6】:

              使用 backgroundworker 组件在单独的线程中运行批处理,这不会影响 UI 线程。

              【讨论】:

                【解决方案7】:

                我想重申我之前评论者指出的内容:请尽可能避免使用 DoEvents(),因为这几乎总是一种“黑客”形式,会导致维护噩梦。

                如果你走 BackgroundWorker 道路(我建议),如果你想调用控件的任何方法或属性,你将不得不处理对 UI 的跨线程调用,因为这些是线程仿射的并且必须是仅从创建它们的线程调用。酌情使用 Control.Invoke() 和/或 Control.BeginInvoke()。

                【讨论】:

                  【解决方案8】:

                  如果您在后台/工作线程中运行,您可以在您的一个 UI 控件上调用 Control.Invoke 以在 UI 线程中运行委托。

                  Control.Invoke 是同步的(等待委托返回)。如果您不想等待,请使用.BeginInvoke() 仅对命令进行排队。

                  .BeginInvoke() 的返回值允许您检查方法是否完成或等待它完成。

                  【讨论】:

                    【解决方案9】:

                    Application.DoEvents() 或可能在单独的线程上运行批处理?

                    【讨论】:

                    • 我会使用 BackgroundWorker 并让批处理在单独的线程上运行。我不喜欢 Application.DoEvents() 因为即使你这样做了,当你没有这样做并且你正在处理工作时,你的 UI 并不是完全没有响应,并且可能对你的用户显得“挂起”。跨度>
                    • 不要这样做——这是一种真正的代码味道。我从未在生产代码中看到过 Application.DoEvents() 我认为“这是最简单、最正确的解决方案”。
                    • 如果您使用 CLR Profiler 2.0,您会注意到使用 DoEvents 会创建很多句柄,这些句柄会一直存在!我建议像 jolson 所说的那样使用 BackgroundWorker。
                    【解决方案10】:

                    DoEvents() 是我一直在寻找的,但我也对后台工作人员的答案投了赞成票,因为这看起来是一个很好的解决方案,我将进行更多调查。

                    【讨论】:

                    • 一定要考虑BackgroundWorker——每次我遇到DoEvents(),解决后续问题从来都不是一件有趣的事。
                    猜你喜欢
                    • 2013-08-02
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-03-19
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-02-03
                    相关资源
                    最近更新 更多