【问题标题】:Update GUI from a non-GUI-Thread -> Not working with BeginInvoke()从非 GUI 线程更新 GUI -> 不使用 BeginInvoke()
【发布时间】:2014-03-17 16:10:55
【问题描述】:

我创建了一个简单的 WindowsForms 应用程序,其中有一个 button 和一个 label。 如果我单击按钮,则标签应显示从 1 到 100000 的数字。

但是如果我点击button,那么GUI 会冻结,直到程序计数到 100000,然后标签显示 100000,并且 GUI 停止冻结。

从 1 到 100000 的计数在新线程(不是 GUI 线程)中执行,然后更改 labeltext 我尝试使用 BeginInvoke,但它不起作用...

using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e) {

            new Thread(DoSomethingExpensive).Start();

        }

        void DoSomethingExpensive() {

            for (int i = 0; i < 100000; i++) {
                this.label1.BeginInvoke((Action)(() => {
                    label1.Text = "" + i;
                    }
                    ));
            }

        }

    }
}

【问题讨论】:

  • @Vkt0rS。在这种情况下,这不会真正改变任何东西,或者提供任何特别有用的工具。虽然总体上很好,但 BGW 并不是真正为处理这个特殊问题而设计的。
  • @Servy 怎么样?这似乎是为 bgw 设计的确切用例。您想在长时间运行的过程中向 UI 线程发送更新。
  • @DanielMann 您为什么不亲自尝试一下,看看是否可以将此代码重新编写为利用 BGW 工具的有效且可行的解决方案。您会看到它与此问题中的问题完全相同。

标签: c# multithreading user-interface begininvoke


【解决方案1】:

BeginInvoke 是异步的。这意味着它将在 UI 线程中发生的操作排队,然后继续其业务。在 UI 线程中排队 100,000 个操作实际上并不需要很长时间。实际上,它比实际执行每个操作花费的时间要少得多。这意味着队列最终会被所有这些更新文本的请求淹没。当添加新请求时,例如,重新绘制表单以显示新值或响应表单上的鼠标单击事件的请求,它被放置在队列的末尾,在成千上万的其他请求之后。

如果您使用Invoke,而不是BeginInvoke,那么后台工作人员将不会继续对下一个项目进行排队,直到前一个项目在 UI 线程中完成运行,从而使其无法领先于 UI线程并淹没队列。这意味着任何其他 UI 事件,例如实际重绘屏幕的事件,在队列中最多等待一个项目。

【讨论】:

  • 这不是很准确,BeginInvoke() 请求也被放置在消息队列的末尾。问题是有 两个 队列,Windows 实现的消息队列和存储委托的调用队列。单个调用将导致 UI 线程消耗 所有 调用队列条目。如果在前一个委托目标完成执行时还有另一个准备好被调用,则它永远不会停止。
  • @HansPassant 我不明白你的最后一句话。你能改写一下吗?
  • 它循环直到调用队列为空。它永远不会变空。
  • @HansPassant 为什么它不会变空?除非有一些其他不相关的操作正在用大量的代表填充队列,在这种情况下,这将是要解决的问题。
  • 因为执行委托目标所花费的时间比工作线程添加另一个所需的时间要长。
【解决方案2】:

对于每个值都应该进行线程上下文切换。如果你这样做 100000 次,UI 线程就会冻结。这是由异步调用引起的。 UI 线程除了 EXECUTE this delegates 之外什么都没有做。 UI 没有时间从 MessagePump 获取工作项,直到调用最后一个委托。控件的更新由 MessagePump 通过 WindowMessages 触发。

现在将最后一个 delgate 的值设置为控件。

【讨论】:

  • For every value a thread context switch is done. 不,实际上,情况并非如此。事实上,如果是这样,那么代码就会真正起作用。问题是没有足够快的上下文切换。您说 UI 线程除了执行这些委托之外几乎什么都不做是正确的,但它不一定会在执行任何其他操作之前执行所有这些委托(尽管它可能会执行)。您也根本没有解释 OP 如何修复他的代码。
【解决方案3】:

这是一种天真的 ReactiveUI 方式:

public partial class Form1 : Form
{
    IDisposable subscription;
    IObservable<long> sequence;

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (subscription != null)
            subscription.Dispose();

        sequence = Observable
            .Interval(TimeSpan.FromMilliseconds(1))
            .Take(10000); // generate a timed sequence

        subscription = sequence // act upon the sequence
            .ObserveOn(SynchronizationContext.Current)
            .Subscribe(x => label1.Text = x.ToString());
    }
}

取决于当前的reactiveui-winforms

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-10
    相关资源
    最近更新 更多