首页 > 解决方案 > 频繁调用 BeginInvoke 时 C# 缓慢的 UI 性能

问题描述

我有一个名为 ProxyTesterForm 的主窗体,它有一个子窗体 ProxyScraperForm。当 ProxyScraperForm 抓取新的代理时,ProxyTesterForm 通过异步测试抓取的代理来处理事件,并在测试后将代理添加到作为 DataGridView 的数据源的 BindingList 中。

因为我要添加到在 UI 线程上创建的数据绑定列表,所以我在 DataGridView 上调用 BeginInvoke,因此更新发生在适当的线程上。

在我将在下面发布的方法中没有 BeginInvoke 调用,我可以在处理过程中在屏幕上拖动表单,它不会卡顿并且很流畅。对于 BeginInvoke 调用,它的作用正好相反。

我对如何解决它有一些想法,但想在 SO 上听到比我更聪明的人的意见,所以我妥善解决了这个问题。

  1. 使用 semaphore slim 来控制同时更新的数量。

  2. 将异步处理的项目添加到我将在下面发布的方法范围之外的列表中,并在 Timer_Tick 事件处理程序中迭代该列表,每 1 秒为列表中的每个项目调用 BeginInvoke,然后清除该列表并清洗,冲洗,重复直到工作完成。

  3. 放弃数据绑定的便利,走虚拟模式。

  4. 其他人可能会在这里提出任何建议。

    private void Site_ProxyScraped(object sender, Proxy proxy)
    {
        Task.Run(async () =>
        {
            proxy.IsValid = await proxy.TestValidityAsync(judges[0]);
            proxiesDataGridView.BeginInvoke(new Action(() => { proxies.Add(proxy); }));
        });
    }
    

标签: c#

解决方案


在 Windows 中,每个具有 UI 的线程都有一个消息队列 - 此队列用于为该线程的窗口发送 UI 消息,这些消息包括鼠标移动、鼠标向上/向下等内容。

在每个 UI 框架的某个地方都有一个循环,它从队列中读取消息,处理它,然后等待下一条消息。

有些消息的优先级较低,例如鼠标移动消息只有在线程准备好处理它时才会生成(因为鼠标往往会移动很多)

BeginInvoke 也使用这种机制,它发送一条消息告诉循环它需要运行的代码。

您正在做的是用您的 BeginInvoke 消息淹没队列,而不是让它处理 UI 事件。

标准的解决方案是限制 BeginInvoke 调用的数量,例如,收集您需要添加的所有项目并使用一个 BeginInvoke 调用将它们全部添加。

或者分批添加,如果您每秒只对这一秒内找到的所有对象进行一次 BeginInvoke 调用,您可能不会影响 UI 响应性,并且用户将无法区分。


推荐阅读