【问题标题】:Is there a quicker way of updating the UI when iterating through a large list?迭代大型列表时是否有更快的更新 UI 的方法?
【发布时间】:2014-06-06 03:24:23
【问题描述】:

我对包含大约 35 万个项目的字典进行了三个测试。我需要看看我可以在 30 秒内迭代多少项。

我的初始测试只是遍历字典,没有检查,也没有 UI/控制台更新。它在开始测试后仅一秒钟就完成了。

第二个测试是将计数写入控制台。已达到约 23,600 项。

foreach (KeyValuePair<UInt64, MftFile> entry in mftFiles)
{
    fileCount++;
    Console.WriteLine(fileCount.ToString());
}

然后我测试了在使用计数更新表单时它的运行速度。它只达到了 16000 多件。

foreach (KeyValuePair<UInt64, MftFile> entry in mftFiles)
{
    fileCount++;
    MessageReceived(this, GetMessageReceivedEventArgs("Proactive Checks - RIFT",
    string.Format("Analyzing Drive {0} - MFT Record {1} of {2}", drive.Name, fileCount.ToString(), mftFiles.Count.ToString()), string.Empty));
}

在循环中执行任何类型的条件逻辑都会大大减慢循环速度。我最初在循环之外创建了一个秒表,并在循环内部检查它以查看它何时到达某个时间。一分钟多过去了,它只迭代了大约 5000 多个项目。

有一个 C++ 应用程序与我正在做的事情类似。它只需不到 60 秒的时间就可以遍历所有 300,000 个项目、更新 UI、查找所有重复项并为每个重复项创建哈希。

有没有更好的方法来遍历字典?

【问题讨论】:

  • 这取决于您在 MessageRecieved 方法中执行的操作,但您可能希望对 UI 进行一些异步更新,因此它不会延迟 for 循环。此外,您可以使用模数运算符仅更新 UI,例如每 5 次或每 10 次循环。

标签: c# winforms loops dictionary


【解决方案1】:

您已经在使用更快的方法来遍历字典。没有其他更快的方法来遍历字典。见What is the best way to iterate over a Dictionary in C#?

如果您想在达到特定时间后停止,那么这可能会起作用

var t = new Thread(() => {
   foreach (KeyValuePair<UInt64, MftFile> entry in mftFiles)
   {
       fileCount++;
       Console.WriteLine(fileCount.ToString());
   }
});
t.Start();
// Sleep the current thread as long as you want to run the task
Thread.Sleep(/* Specify time in milliseconds */);
// After that abort the Thread to exit the job
t.Abort();

进一步使用StringBuilder而不是String.Format,因为它必须解析需要时间的字符串。

编辑 快速更新 UI 在其他线程中运行 MFT 扫描并在第二个线程中观察扫描。不是在每个文件上更新 UI,而是在特定时间后更新。通过使用以下代码,我在更新 UI 方面取得了良好的表现。

public MyForm() {
    InitializeComponent();

    scanProgress = new Action(() => {
        MessageReceived(this, GetMessageReceivedEventArgs("Proactive Checks - RIFT", string.Format("Analyzing Drive {0} - MFT Record {1} of {2}", drive.Name, cMft, count, string.Empty));
    });
}

Action<> scanProgress;

int cMft = 0;
int count;
void ScanMft() {
    count = mftFiles.Count;
    foreach (KeyValuePair<UInt64, MftFile> entry in mftFiles) {
        cMft++;
        /* Scan MFT */
    }
}

void WatchScan() {
    while (cMft < count) {
       Thread.Sleep(200); 
       this.BeginInvoke(scanProgress);
    }
}

void RunScan() {
    new Thread(ScanMft).Start();
    new Thread(WatchScan).Start();
}

【讨论】:

  • 我想我应该问是否有更快的方法来更新 UI。它非常慢,它所做的只是更改标签的文本。
【解决方案2】:

那里有 2 个问题,如何更快地做到这一点(你没有为此提供足够的代码,你的瓶颈肯定不应该是自己迭代一个集合,而且你得到的那些数字非常缓慢,是你在调试中测试?从来没有在调试中进行性能测试!)

发布更多代码并在发布时对其进行测试,不要发布示例测试而是您的实际代码,我们可以提供帮助。

问题的第二部分是,如何避免在处理时阻塞 UI,这很简单,不要在主线程上处理,将整个函数移动到另一个线程并定期(每 100 次?插入) 使用 Control.BeginInvoke http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke(v=vs.110).aspx更新控件

这意味着 UI 在您的处理过程中是完全反应式的,并且只会在实际更新时冻结很小的时间片,因此它会一直响应。

如果所有这些都不够,另一个优化是,不要做你不需要做的工作!坚持我所说的(在另一个线程上处理),但除了第一个 200 或 300 个元素之外不要更新 UI,然后实现一个只显示当前应该看到的内容的虚拟化控件(在 WPF 中做微不足道,但我假设您必须在 winform 中自行开发或购买组件)。请注意,如果您处于项目的早期阶段,我强烈建议您切换到 WPF,因为您的应用程序听起来可以使用它(不是带有几个按钮/标签的琐碎业务应用程序)

编辑:为了清楚起见,枚举字典不可能是你的瓶颈,我刚刚在我的中档机器上进行了测试,我在 . . 12 毫秒!

编辑 2:但是 Console.WriteLine 肯定是你的瓶颈,这就是为什么你不应该在测试时引入不相关的 API,迭代到下一个项目 = 没有工作,建立字符串 = 一点工作,实际上通过它到控制台打印 = 大量工作

【讨论】:

    【解决方案3】:

    您已经回答了自己的问题。你已经证明你可以在一秒钟内遍历字典,所以听起来你不需要提高速度——速度慢与字典无关。这是 MessageReceived 函数调用。我不知道那个函数是做什么的,所以我无法解决它为什么慢。

    我也会尝试删除它:

    mftFiles.Count.ToString()
    

    您可以在循环之外执行此操作并存储该值,这可能会有所帮助。

    【讨论】:

    • 调用该事件或直接更新 UI 都没有关系。它更新表单上的标签。结果是一样的。
    • 我不关注。您正在一遍又一遍地更新表单上的标签,每秒数百次?如果是,那可能就是问题所在。尝试将 MessageReceived 函数调用放入:if (filecount % 1000 == 0) {}。这可能更合理。
    • @ernest - 顺便说一句,为什么要投反对票?我准确地回答了你的问题。就像这里的其他人一样。
    【解决方案4】:

    您可以创建一个工作线程并将 foreach 循环放入其中。通过这样做,UI 和 foreach 循环将在不同的线程上运行,因此您无需更新 UI。

    另一种替代方式(我不推荐,因为它有其自身的后果)是放置

    Application.DoEvent();
    

    在 foreach 循环中。这样做,您的 UI 将随着每次迭代而更新。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多