【问题标题】:Method on BackgroundWorker is freezing GUIBackgroundWorker 上的方法正在冻结 GUI
【发布时间】:2011-04-01 07:03:57
【问题描述】:

我正在尝试运行从指定文件夹获取文件列表并在 DataGridView 中表示它的方法。该方法在 BackgroundWorker 中运行,因此我希望 GUI 保持活动状态。但它仍然冻结。这是一个例子:

private void startScan_Click(object sender, EventArgs e)
{
    bckgrFileScanner.RunWorkerAsync();
}

private void bckgrFileScanner_DoWork(object sender, DoWorkEventArgs e)
{
//for each folder in list perform this function, which scans folder and gets all files
    for (int i = 0; i < folderList.Items.Count; i++)
    {
        GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);

    }
 }

public static void GetFileList(ref List<FileInfo> fList, string fPath, BackgroundWorker scanner)      
{
     DirectoryInfo di = new DirectoryInfo(fPath);
     FileInfo[] fi = di.GetFiles();

     foreach (FileInfo fiTemp in fi)
     {                
          if (fiTemp.Name.StartsWith("~$") == false)
          {
              //adds items to list of all scanned files
              fList.Add(fiTemp);
              //reports file name to ProgressChanged method
              scanner.ReportProgress(0, fiTemp);
          }
     }
     DirectoryInfo[] dFolders = di.GetDirectories();

     //use recursion for all subfolders
     foreach (DirectoryInfo d in dFolders)
     {
          GetFileList(ref fList, d.FullName, scanner);
     }
}

private void bckgrFileScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //userstate is filename, so add it to table
    filesDataGrid.Rows.Add(e.UserState.ToString());  
}

【问题讨论】:

    标签: c# .net user-interface backgroundworker freeze


    【解决方案1】:

    不要在循环的每一行更新您的 reportprogress 事件。 相反,每 2 次或更多次迭代调用一次 ReportProgress(如果您有 200000,最好的方法是计算 et step 以不在每一行更新) 并在整个过程完成后(在完成事件中)填充网格,以更新所有行。我认为进度只是为了更新进度,而不是将一堆数据填充到控件中,但我可能是错的: 来自here

    提示 您对 BackgroundWorker 的了解可能比您想象的要多 班级。 BackgroundWorker 的名称可能表明它更多 比实际复杂。还有更多关于线程的细节 并中止调用,但是一旦您了解 BackgroundWorker 只是 Windows 窗体中线程的结构“覆盖”,它相当 直觉的。步骤如下:

    首先,使用参数调用 RunWorkerAsync。你可以传递任何参数 到BackgroundWorker上的这个方法,包括null。它必须 从对象继承,一切都是如此。

    其次,运行自定义处理。您昂贵的代码在 DoWork 方法。在您的程序执行时在此处插入暂停 计算。

    第三,结束。处理完成后,RunWorkerCompleted 叫做。在此方法中,您会收到结果。这样,您的 BackgroundWorker 对象修改另一个线程上的对象,而您 完成后收到。

    我认为当需要在短时间内进行太多更新时它会挂起,甚至调用 Apllication.DoEvents() 也不是一直有效。 我希望它会有所帮助,我知道有点晚了,但总比没有好;)

    【讨论】:

    • 我已经用不同的方式修复了它(带有分页功能的小桌子),无论如何都打赌谢谢。
    【解决方案2】:

    这个(简化的)示例对我有用,并且有一个可爱的响应式 UI:

        BackgroundWorker m_objWorker = new BackgroundWorker();
    
        public FormBackgroundWorkerExample()
        {
            InitializeComponent();
            m_objWorker.WorkerReportsProgress = true;
            m_objWorker.DoWork += new DoWorkEventHandler(m_objWorker_DoWork);
            m_objWorker.ProgressChanged += new ProgressChangedEventHandler(m_objWorker_ProgressChanged);
        }
    
        private void btnStart_Click(object sender, EventArgs e)
        {
            m_objWorker.RunWorkerAsync();
        }
    
        private void m_objWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //for each folder in list perform this function, which scans folder and gets all files
            for (int i = 0; i <= 100; i++)
            {
                m_objWorker.ReportProgress(i, "FooBar");
                Thread.Sleep(1000);
            }
        }
    
        private void m_objWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
            dataGridView1.Rows.Add(e.UserState.ToString()); 
        }
    

    也许这会让你知道你的问题在哪里?

    编辑:您可能想在没有数据网格的情况下尝试它,只是为了尝试隔离问题。

    【讨论】:

    • 我之前将 backgroundworker 用于其他方法,与我在这里使用的风格相同,它工作得很好。我在想也许它与递归有关,但是从我的方法中删除它后它仍然冻结。
    • 如果不向数据网格添加行,它可以正常工作,不会冻结,所以存在问题,但我不知道为什么,因为我之前使用过类似的代码,其中表是从 ProgressChanged 更新的,没有那里注意到冻结。
    • @andree 我已经更新了示例;向数据网格添加行在这里没有问题。您的代码示例中是否还有其他可能相关的内容?
    • @andree 你注册到 DataGrid 事件了吗?如果事件处理程序执行长时间运行的操作,这将解释您的应用冻结的原因。
    • 问题是 ProgressChanged 执行添加了很多新行 - 每个文件一个。 “注册到 DataGrid”事件到底是什么意思?
    【解决方案3】:

    可能是因为您锁定了后台工作程序本身。

    在你的 DoWork 方法中

    GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
    

    通常你应该可以从你的 DoWork 方法中访问 bckgrFileScanner,直接调用它为 bckgrFileScanner.ReportProgress( .... .... )

    当像你一样传递它时,它现在会在后台工作线程上报告进度,这与 UI 线程不同。 (谁拥有 bckgrFileScanner)

    编辑澄清:

    1. 您的 UI 线程拥有 bckgrFileScanner 并触发 RunWorkerAsync()
    2. DoWork 中发生的事情现在在它自己的线程上。
    3. DoWork 线程正在“窃取”变量 bckgrFileScanner
    4. 现在 ReportProgress 在 DoWork 线程而不是 UI 线程上触发

    【讨论】:

    • GetFileList(参考扫描文件,文件夹列表.Items[i].ToString(),bckgrFileScanner);我已经有了这条线,但是当使用递归时,我改为调用扫描仪,因为我无权访问 GetFileList 方法中的 bckgrFileScanned。抱歉,我不太明白我需要在这里更改什么。
    • 有什么理由让你想让那个方法静态?使其成为非静态并直接使用 bckgrFileScanner 而不是“scanner.ReportProgress”。如果您打算将它用作其他来源/类的工具,那么您可以执行类似“public static class ScanDirectories{ .. }”的操作,让后台工作人员执行它的目录遍历,并添加返回完整列表时它通过一个事件完成。或者(如果您使用 wpf/silverlight)将项目添加到 ObservableCollection 并在其上监听更改的事件。
    【解决方案4】:

    您正在扫描仪上调用ReportProgress。不应该是bckgrFileScanner吗?

    编辑

    scannedFiles 列表是否有可能数据绑定到 UI?如果是这样,对列表的更改会导致 UI 更新。

    【讨论】:

    • 在 GetFileList 方法中,“扫描仪”是对 bckgrFileScanner 的引用,这就是为什么我将扫描仪称为 bckgrFileScanner。此外,在这种方法中,我什至无法访问 bckgrFileScanner。
    • 啊,是的,由于横向滚动,我错过了。您可以使用 Debug.WriteLine(Thread.CurrentThread...) 来跟踪正在执行代码的线程,以便检查您的 UI 线程是否真的被锁定或其他东西被破坏了。
    • 不,scannedFiles 列表用作整个表单的全局变量。
    【解决方案5】:

    可能的原因:

    我想你只是想报告文件名:

    //reports file name to ProgressChanged method
    scanner.ReportProgress(0, fiTemp.Name);
    

    或者DoWork中的folderList是一个UI控件:

    for (int i = 0; i < folderList.Items.Count; i++)
    {
        GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
    }
    

    为什么不将文件夹列表传递给 RunWorkerAsync 方法。

    【讨论】:

    • 没错,我的错。不是冻结的原因。
    • folderList 是 ListBox,包含所有文件夹。尝试将 folderList 项目作为字符串数组传递给 RunWorkerAsync - 仍然没有运气。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2011-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多