BackgroundWorker 冻结了我的 UI,而 BeginInvoke 似乎以龟速 [英] BackgroundWorker freezes my UI and BeginInvoke seems to go turtle speed

查看:31
本文介绍了BackgroundWorker 冻结了我的 UI,而 BeginInvoke 似乎以龟速的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须做一个看似简单的任务:1) 列出目录(及其子目录)中的所有文件,2) 在多行文本框中显示所有文件,然后 3) 在每个文件中执行一些操作.由于 2 个问题,我被困在 2),这是我所拥有的:

I have to do a very seemingly easy task: 1) list all files in a directory (and its sub-directories), 2) show them all in a multiline textbox and then 3) do some stuff inside each file. I'm stuck at 2) due to 2 problems, here's what I have:

  1. Form1.cs 是我管理 UI 并启动 BackgroundWorker 的地方运行 Logic.cs 的主要功能
  2. DependencyMapper.cs 是......好吧,我在这里做文件夹/文件的事情(在 Fetch() 中)并调用一个 Form1 使用 BeginInvoke 将每一行(当前文件的名称)填充到 Form1's 文本框的方法.
  1. Form1.cs is where I manage the UI and start a BackgroundWorker that runs the Logic.cs's main function
  2. DependencyMapper.cs is... well, where I do the folders/files thing (in Fetch()) and call a Form1 method that populates each line (the current file's name) into a Form1's textbox using BeginInvoke.

少说话多写代码.这是我的代码的精简版:

Less talk and more code. This is the skinny, awfully working version of my code:

Form1.cs

public partial class Form1 : Form
{
    public DependencyMapper dep;
    BackgroundWorker bwDep;

    public Form1()
    {
        // I read here in SO to try put the BW stuff here don't know why, but hasn't helped.
        InitializeComponent();

        bwDep = new BackgroundWorker();
        bwDep.DoWork += bwDep_DoWork;
        bwDep.RunWorkerCompleted += bwDep_RunWorkerCompleted;
    }

    private void button1_Click(object sender, EventArgs e)
    {
            bwDep.RunWorkerAsync();
    }
    void bwDep_DoWork(object sender, DoWorkEventArgs e)
    {
        dep.Fetch(extensions);
    }
    public void SendBack(string msg) // To receive Fetch()s progress
    {
        textBox2.BeginInvoke(new Action(() =>
        {
            textBox2.Text += msg + "\r\n";
            textBox2.SelectionStart = textBox2.Text.Length;
            textBox2.ScrollToCaret();
        }));
    }
}

DependencyMapper.cs

public class DependencyMapper
{
    private Form1 form;
    public DependencyMapper(Form1 form1)
    {
        this.form = form1;
    }
    public void Fetch()
    {
        DirectoryInfo folder = new DirectoryInfo(form.Texto1);
        FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            form.SendBack(files[i].FullName); // Kind of talking back to the UI through form's reference and SendBack method which uses BeginInvoke.
        }
    }
}

那么,我的应用程序可以运行吗?是的,但有两个我无法解决的大问题:

So, does my app work? Yes, but two huge problems I can't solve:

  1. 它冻结了用户界面(wtf lazy BackgroundWorker?).不完全是因为文本框正在一个一个地添加每个文件,但就像它应该的那样,但我无法移动窗口或单击任何按钮.
  2. 速度太慢了.我肯定做错了什么.我的应用程序目前以大约每秒 10 个文件的速度填充文本框.我正在编码它以在数百个文件中找到特定的文本片段...omg
  1. It freezes the UI (wtf lazy BackgroundWorker?). Not completely because the textbox is adding each file one by one but like it's supposed to but I can't move the window around or click any buttons.
  2. It's veeery slow. Definitely I'm doing something wrong. My app currently fills the textbox at a rate of 10 files per second approx. And I'm coding it to find particular snippets of text in hundreds of files...omg

PS:在使用 BackgroundWorker 之前,我使用的是 Thread:UI 根本没有冻结,但文本框填充率很慢.这就是为什么我决定使用 BackgroundWorker 冒险,但它只会带来问题 #1.

PS: Before using BackgroundWorker I was using Thread: the UI didn't freeze at all but the textbox populating ratio was as slow. That's why I decided to venture with BackgroundWorker which only brought problem #1.

谢谢.

推荐答案

您正在喷火 UI 线程,产生结果的速度远远快于 UI 显示它们的速度.它可能开始时还算不错,但随着时间的推移会变得越来越糟.您强制 TextBox 重新分配内存以为附加字符串寻找空间,复制原始文本并附加新文本.一旦文本框包含一兆字节的文本,给予或接受,这将严重开始拖动.System.String 类有同样的问题.其中有 StringBuilder 作为修复,TextBox 没有类似的东西.

You are fire-hosing the UI thread, producing results far faster than the UI can display them. It might start off reasonably well, but gets worse and worse over time. You force the TextBox to reallocate memory to find room for the appended string, copy the original text and append the new text. This will seriously start to drag once the textbox contains a megabyte of text, give or take. The same kind of problem that the System.String class has. Which has StringBuilder as the fix, TextBox doesn't have anything similar.

通常的症状是 UI 线程将开始消耗 100% 的内核.在某些关键时刻,它会完全紧张,不再重新绘制 UI 并且对输入无响应.因为每次更新 Text 属性时,它都有另一个要调用的委托.没有时间去做低优先级的工作,比如绘制和清空消息队列.调用队列本身开始落后,收集越来越多等待处理的调用请求.在极端情况下,程序会因 OutOfMemoryException 而崩溃,但这需要很长时间.即使后台工作完成后,UI 线程也会在此之后忙碌一段时间,试图减少积压.

The usual symptoms are that the UI thread will start burning 100% core. At some critical point it goes completely catatonic, not repainting the UI anymore and getting unresponsive to input. Because every time it updated the Text property, it has yet another delegate to invoke. There's no time left to do the lower priority jobs, like painting and emptying the message queue. The invoke queue itself starts falling behind, gather more and more invoke requests that are waiting to be processed. In extreme cases that will crash your program with OutOfMemoryException but that takes a very long time. Even when the background worker finishes, the UI thread is busy for quite a bit longer after that, trying to work down the backlog.

用户界面通常是无功能的,甚至在它撞墙之前也是如此.用户只是盯着一个令人眼花缭乱的快速滚动文本框,实际上不允许阅读任何内容.使用文本框本身会适得其反,用户编辑列表没有任何意义.

The UI is in general non-functional, even before it hits the wall. The user is just staring at a blindingly fast scrolling textbox that does not permit actually reading anything. Using a textbox in itself is very counter-productive, it doesn't make any sense for the user to edit the list.

显然,您需要以不同的方式执行此操作.至少在调用之前在 StringBuilder 中收集一大堆文件.这将使 TextBox 不得不经常重新分配.每 50 毫秒调用一次以上是没有意义的,这与人眼可以看到变化而不会变得模糊的速度一样快.大多数执行此操作的程序只显示被迭代的文件的示例,以便用户对进度有一些反馈.在这种情况下,在 StringBuilder 中收集所有数据并且在文件迭代完成之前不更新 TextBox 是完全合理的.

Clearly you need to do this differently. At least gather a whole bunch of files in a StringBuilder before you invoke. That will give the TextBox a break having to reallocate so often. It never makes sense to invoke more often than once every 50 msec, that's about as fast as a human eye can see changes without them turning into a blur. Most programs that do this just show a sample of the files getting iterated so that the user has some feedback on progress. In which case it is entirely reasonable to gather all the data in a StringBuilder and not update the TextBox until the file iteration completes.

这篇关于BackgroundWorker 冻结了我的 UI,而 BeginInvoke 似乎以龟速的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆