C# BackgroundWorker冻结了我的用户界面,BeginInvoke似乎以极快的速度运行

C# BackgroundWorker冻结了我的用户界面,BeginInvoke似乎以极快的速度运行,c#,multithreading,winforms,backgroundworker,C#,Multithreading,Winforms,Backgroundworker,我必须完成一项看似简单的任务:1)列出目录(及其子目录)中的所有文件,2)在多行文本框中显示所有文件,然后3)在每个文件中执行一些操作。由于两个问题,我陷入了困境,以下是我的问题: Form1.cs是我管理UI并启动BackgroundWorker 它运行Logic.cs的主要功能 DependencyMapper.cs是。。。在这里,我执行文件夹/文件操作(在Fetch())并调用Form1方法,该方法使用BeginInvoke将每一行(当前文件的名称)填充到Form1的文本框中 少说话多代码

我必须完成一项看似简单的任务:1)列出目录(及其子目录)中的所有文件,2)在多行文本框中显示所有文件,然后3)在每个文件中执行一些操作。由于两个问题,我陷入了困境,以下是我的问题:

  • Form1.cs
    是我管理UI并启动
    BackgroundWorker
    它运行
    Logic.cs
    的主要功能
  • DependencyMapper.cs
    是。。。在这里,我执行文件夹/文件操作(在
    Fetch()
    )并调用
    Form1
    方法,该方法使用
    BeginInvoke
    将每一行(当前文件的名称)填充到
    Form1的
    文本框中
  • 少说话多代码。这是我的代码的精简版:

    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();
            }));
        }
    }
    
    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.
            }
        }
    }
    
    dependencMapper.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();
            }));
        }
    }
    
    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.
            }
        }
    }
    
    公共类依赖项apper
    {
    私人表格1表格;
    公共从属上诉人(表格1表格1)
    {
    this.form=form1;
    }
    公共空取()
    {
    DirectoryInfo文件夹=新的DirectoryInfo(form.Texto1);
    FileInfo[]files=folder.GetFiles(“*.*”,SearchOption.AllDirectories);
    for(int i=0;i
    那么,我的应用程序能用吗?是的,但有两个大问题我无法解决:

  • 它冻结UI(wtf lazy
    BackgroundWorker
    ?)。不完全是因为文本框一个接一个地添加每个文件,但就像它应该做的那样,但我不能移动窗口或单击任何按钮
  • 速度很慢。我肯定做错了什么。我的应用程序目前以每秒大约10个文件的速度填充文本框,我正在对其进行编码,以便在数百个文件中找到特定的文本片段…天哪
  • PS:在使用
    BackgroundWorker
    之前,我使用的是
    Thread
    :UI根本没有冻结,但文本框填充率同样缓慢。这就是为什么我决定与
    BackgroundWorker
    合作,这只会带来问题1

    谢谢。

    您正在使用UI线程,生成的结果比UI显示的结果快得多。一开始可能还算不错,但随着时间的推移,情况会越来越糟。您强制文本框重新分配内存,以便为附加的字符串找到空间,复制原始文本并附加新文本。一旦文本框包含一兆字节的文本,这将严重地开始拖拽。与System.String类存在的问题相同。TextBox没有任何类似的功能

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

    UI通常是不起作用的,甚至在它碰到墙之前。用户只是盯着一个令人眼花缭乱的快速滚动文本框,不允许实际阅读任何内容。使用文本框本身会适得其反,用户编辑列表没有任何意义


    显然,你需要以不同的方式来做。在调用之前,至少在StringBuilder中收集一大堆文件。这将给文本框一个机会,让它不得不经常重新分配。每50毫秒调用一次以上是没有意义的,这大约是人眼看到变化而不变成模糊的速度。大多数这样做的程序只是显示一个迭代文件的示例,以便用户对进度有一些反馈。在这种情况下,在StringBuilder中收集所有数据并在文件迭代完成之前不更新文本框是完全合理的。

    在表单初始化之前,尝试放置一个线程(后台)来准备文件夹和文件列表。考虑在表单构造或全局级别上传递列表的方法。
    dep.Fetch(extensions)什么是dep?什么是扩展?带参数的Fetch函数在哪里?请张贴准确的密码。考虑只将最终Relt[Cord]String []/Calp>传递给窗体,如果这是适用的。当需要在后台做某件事时,线程很方便。实际上,您并没有从中受益,因为您一直在与UI对话。真正的好处是完全在后台执行繁重的操作,并在完成后将结果传递给UI。@请记住,使用当前进度更新UI线程没有什么错。这就是
    backgroundworker::ReportProgress()
    的目的。出于某种原因,他没有使用它,但在他的代码(至少是发布的代码)中,UI线程上没有繁重的工作。@AviTurner在我看来,这肯定会出错:
    GetFiles(“**”),SearchOption.AllDirectories)
    如果输入
    C:\
    ,则这可能需要很长时间。每个文件名都被
    发送回
    到UI。我放弃了更新用户的进度(我仍然在编写我想做的实际逻辑),而是将结果打包发送回。但这是一个令人难以置信的回答,给了我一个广阔的视角,谢谢。