为什么即使我使用BackgroundWorker,我的C ++ / CLI应用程序也没有响应?

时间:2015-01-09 15:27:48

标签: winforms visual-c++ treeview backgroundworker

我只是尝试使用C ++ / CLI学习一些Windows窗体的内容以供参考。我使用默认的Windows窗体选项在VS 2010中创建了一个名为LibraryScan的项目,并且我已经使用一些控件修改了表单。我所做的所有代码更改都在Form1.h中(见下文)。我已经添加了整个Form1.h,因为我想,如果您刚刚创建了一个普通的VS 2010 C ++ / CLI Windows窗体应用程序,您可以将自动生成的Form1.h替换为下面的内容(尽管有以下内容)要考虑的图像列表; item [0]是通用文档图标,[1]是一个封闭的文件夹,[2]是一个打开的文件夹。)

基本上你使用"浏览..."按钮选择文件夹,然后按"扫描"按钮,目的是它将通过根文件夹及其子文件夹进行递归,以查找其中的所有文件。每个文件的名称都添加到多行TextBox中,树结构在TreeView中生成。

我遇到的问题是,没有这条线:

System::Threading::Thread::Sleep(1);

在listFolder()函数中,UI在扫描文件夹时没有响应。 TextBox更新正常,但TreeView在扫描完成之前不会显示,并且在扫描时,您无法调整大小或移动应用程序窗口。虽然有点慢,但睡眠(1)也很好!

正如我所说的,我是Windows Forms的新手,但有一些MFC的经验(尽管我27年以上的软件开发经验大部分是嵌入式的东西所以......),包括使用事件泵试图绕过这种东西。然而,到目前为止我所做的阅读似乎表明BackgroundWorker类和RunWorkerAsync()/ ReportProgress()等是C ++ / CLI / Windows Forms的方法,但大多数问题和示例都在C#中,并且所有我用BackgroundWorker搜索没有反应的guis,最终找到了我无法看到的解决方案与我正在做的事情有很大不同!

感谢任何帮助。

#pragma once

ref class ProgressObject;

namespace LibraryScan {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;
    using namespace System::IO;

    /// <summary>
    /// Summary for Form1
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Label^  label1;
    protected: 
    private: System::Windows::Forms::TextBox^  textBox1;
    private: System::Windows::Forms::Button^  button1;
    private: System::Windows::Forms::FolderBrowserDialog^  folderBrowserDialog1;
    private: System::Windows::Forms::OpenFileDialog^  openFileDialog1;
    private: System::Windows::Forms::TextBox^  textBoxFiles;
    private: System::Windows::Forms::Label^  label2;
    private: System::Windows::Forms::Button^  buttonScan;
    private: System::Windows::Forms::TreeView^  treeViewFiles;
    private: System::Windows::Forms::ImageList^  imageList1;
    private: System::ComponentModel::BackgroundWorker^  fileLister;
    private: System::ComponentModel::IContainer^  components;



    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>


#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->components = (gcnew System::ComponentModel::Container());
            System::ComponentModel::ComponentResourceManager^  resources = (gcnew System::ComponentModel::ComponentResourceManager(Form1::typeid));
            this->label1 = (gcnew System::Windows::Forms::Label());
            this->textBox1 = (gcnew System::Windows::Forms::TextBox());
            this->button1 = (gcnew System::Windows::Forms::Button());
            this->folderBrowserDialog1 = (gcnew System::Windows::Forms::FolderBrowserDialog());
            this->openFileDialog1 = (gcnew System::Windows::Forms::OpenFileDialog());
            this->textBoxFiles = (gcnew System::Windows::Forms::TextBox());
            this->label2 = (gcnew System::Windows::Forms::Label());
            this->buttonScan = (gcnew System::Windows::Forms::Button());
            this->treeViewFiles = (gcnew System::Windows::Forms::TreeView());
            this->imageList1 = (gcnew System::Windows::Forms::ImageList(this->components));
            this->fileLister = (gcnew System::ComponentModel::BackgroundWorker());
            this->SuspendLayout();
            // 
            // label1
            // 
            this->label1->AutoSize = true;
            this->label1->Location = System::Drawing::Point(28, 13);
            this->label1->Name = L"label1";
            this->label1->Size = System::Drawing::Size(39, 13);
            this->label1->TabIndex = 0;
            this->label1->Text = L"Folder:";
            // 
            // textBox1
            // 
            this->textBox1->Location = System::Drawing::Point(74, 13);
            this->textBox1->Name = L"textBox1";
            this->textBox1->Size = System::Drawing::Size(409, 20);
            this->textBox1->TabIndex = 1;
            // 
            // button1
            // 
            this->button1->Location = System::Drawing::Point(489, 13);
            this->button1->Name = L"button1";
            this->button1->Size = System::Drawing::Size(75, 23);
            this->button1->TabIndex = 2;
            this->button1->Text = L"Browse...";
            this->button1->UseVisualStyleBackColor = true;
            this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);
            // 
            // folderBrowserDialog1
            // 
            this->folderBrowserDialog1->Description = L"Select the directory that you want to scan.";
            this->folderBrowserDialog1->ShowNewFolderButton = false;
            // 
            // openFileDialog1
            // 
            this->openFileDialog1->FileName = L"openFileDialog1";
            // 
            // textBoxFiles
            // 
            this->textBoxFiles->Anchor = static_cast<System::Windows::Forms::AnchorStyles>(((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) 
                | System::Windows::Forms::AnchorStyles::Right));
            this->textBoxFiles->Location = System::Drawing::Point(34, 62);
            this->textBoxFiles->Multiline = true;
            this->textBoxFiles->Name = L"textBoxFiles";
            this->textBoxFiles->ScrollBars = System::Windows::Forms::ScrollBars::Both;
            this->textBoxFiles->Size = System::Drawing::Size(596, 419);
            this->textBoxFiles->TabIndex = 3;
            this->textBoxFiles->WordWrap = false;
            // 
            // label2
            // 
            this->label2->AutoSize = true;
            this->label2->Location = System::Drawing::Point(31, 43);
            this->label2->Name = L"label2";
            this->label2->Size = System::Drawing::Size(31, 13);
            this->label2->TabIndex = 4;
            this->label2->Text = L"Files:";
            // 
            // buttonScan
            // 
            this->buttonScan->Enabled = false;
            this->buttonScan->Location = System::Drawing::Point(570, 13);
            this->buttonScan->Name = L"buttonScan";
            this->buttonScan->Size = System::Drawing::Size(75, 23);
            this->buttonScan->TabIndex = 5;
            this->buttonScan->TabStop = false;
            this->buttonScan->Text = L"Scan";
            this->buttonScan->UseVisualStyleBackColor = true;
            this->buttonScan->Click += gcnew System::EventHandler(this, &Form1::buttonScan_Click);
            // 
            // treeViewFiles
            // 
            this->treeViewFiles->Anchor = static_cast<System::Windows::Forms::AnchorStyles>(((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) 
                | System::Windows::Forms::AnchorStyles::Right));
            this->treeViewFiles->ImageIndex = 0;
            this->treeViewFiles->ImageList = this->imageList1;
            this->treeViewFiles->Location = System::Drawing::Point(636, 62);
            this->treeViewFiles->Name = L"treeViewFiles";
            this->treeViewFiles->SelectedImageIndex = 0;
            this->treeViewFiles->Size = System::Drawing::Size(392, 419);
            this->treeViewFiles->TabIndex = 6;
            this->treeViewFiles->AfterCollapse += gcnew System::Windows::Forms::TreeViewEventHandler(this, &Form1::treeViewFiles_AfterCollapse);
            this->treeViewFiles->AfterExpand += gcnew System::Windows::Forms::TreeViewEventHandler(this, &Form1::treeViewFiles_AfterExpand);
            // 
            // imageList1
            // 
            this->imageList1->ImageStream = (cli::safe_cast<System::Windows::Forms::ImageListStreamer^  >(resources->GetObject(L"imageList1.ImageStream")));
            this->imageList1->TransparentColor = System::Drawing::Color::Transparent;
            this->imageList1->Images->SetKeyName(0, L"Generic_Document.png");
            this->imageList1->Images->SetKeyName(1, L"Folder_16x16.png");
            this->imageList1->Images->SetKeyName(2, L"FolderOpen_16x16_72.png");
            // 
            // fileLister
            // 
            this->fileLister->WorkerReportsProgress = true;
            this->fileLister->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::fileLister_DoWork);
            this->fileLister->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &Form1::fileLister_ProgressChanged);
            this->fileLister->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &Form1::fileLister_RunWorkerCompleted);
            // 
            // Form1
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(1040, 493);
            this->Controls->Add(this->treeViewFiles);
            this->Controls->Add(this->buttonScan);
            this->Controls->Add(this->label2);
            this->Controls->Add(this->textBoxFiles);
            this->Controls->Add(this->button1);
            this->Controls->Add(this->textBox1);
            this->Controls->Add(this->label1);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);
            this->PerformLayout();

        }
#pragma endregion
    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
             {
                 // Show the FolderBrowserDialog.
                 System::Windows::Forms::DialogResult result = folderBrowserDialog1->ShowDialog();
                 if ( result == System::Windows::Forms::DialogResult::OK )
                 {
                     String^ folderName = folderBrowserDialog1->SelectedPath;
                     textBox1->Text = folderName;
                     buttonScan->Enabled = true;
                 }
             }

    private: System::Void buttonScan_Click(System::Object^  sender, System::EventArgs^  e)
             {
                 // Scan the folder listed in textBox1 and add the files to textBoxFiles
                 String^ folder = textBox1->Text;
                 fileLister->RunWorkerAsync(folder);
             }

    private: long listFolder(String^ folderName, TreeNode^ rootNode, BackgroundWorker^ worker)
             {
                 // Scan the folder passed in and add the files to textBoxFiles
                 TreeNode^ newNode = gcnew TreeNode(folderName);
                 newNode->ImageIndex = 1;
                 newNode->SelectedImageIndex = 1;
                 worker->ReportProgress(0, gcnew ProgressObject(rootNode, newNode));

                 array<String^>^ file = Directory::GetFiles( folderName );
                 Array::Sort(file);
                 for (int i = 0; i < file->Length; i++)
                 {
                     TreeNode^ fileNode = gcnew TreeNode(file[i]);
                     worker->ReportProgress(0, gcnew ProgressObject(newNode, fileNode));
                     System::Threading::Thread::Sleep(1);
                 }

                 // Now scan the directories under this one
                 array<String^>^ dir = Directory::GetDirectories(folderName);
                 Array::Sort(dir);
                 for (int i = 0; i < dir->Length; i++)
                 {
                     listFolder(dir[i], newNode, worker);
                 }

                 return 0L;
             }

private: System::Void treeViewFiles_AfterCollapse(System::Object^  sender, System::Windows::Forms::TreeViewEventArgs^  e)
         {
             e->Node->ImageIndex = 1;
             e->Node->SelectedImageIndex = 1;
         }

private: System::Void treeViewFiles_AfterExpand(System::Object^  sender, System::Windows::Forms::TreeViewEventArgs^  e) 
         {
             e->Node->ImageIndex = 2;
             e->Node->SelectedImageIndex = 2;
         }
private: System::Void fileLister_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e)
         {
             BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender);
             e->Result = listFolder(safe_cast<String^>(e->Argument), nullptr, worker);
         }
private: System::Void fileLister_ProgressChanged(System::Object^  sender, System::ComponentModel::ProgressChangedEventArgs^  e)
         {
             ProgressObject^ prog = safe_cast<ProgressObject^>(e->UserState);
             if (prog->currentNode != nullptr)
             {
                if (prog->rootNode == nullptr)
                {
                    treeViewFiles->Nodes->Add(prog->currentNode);
                }
                else
                {
                    prog->rootNode->Nodes->Add(prog->currentNode);
                }
                textBoxFiles->AppendText(String::Concat(prog->currentNode->Text, "\n"));
             }
         }

private: System::Void fileLister_RunWorkerCompleted(System::Object^  sender, System::ComponentModel::RunWorkerCompletedEventArgs^  e)
         {
             this->textBoxFiles->AppendText(L"Scan Complete");
         }

    ref class ProgressObject : public Object
    {
    public:
        TreeNode^ rootNode;
        TreeNode^ currentNode;

        ProgressObject(TreeNode^ theRootNode, TreeNode^ theCurrentNode)
            : rootNode(theRootNode),
              currentNode(theCurrentNode)
        {
        }
    };
};
}

1 个答案:

答案 0 :(得分:2)

您的程序受到第3个最常见的线程错误的影响。数字1和2是线程竞争和死锁,BackgroundWorker非常善于帮助您避免那些讨厌的。但它没有做任何事情来帮助你避免3号,一个火管软件错误。这里的精神形象是试图从一个正在运行的消防水管中饮用,无论你多么努力地吞咽,都无法避免洒水。

这里的水是工人生产的TreeNodes。您的文件系统速度很快,可以以很高的速度将它们吐出来。尤其是第二次运行程序时,所有文件数据都来自文件系统缓存。每秒数万。

嘴是程序中的UI线程,它需要将这些节点添加到TreeView并生成一个绘制以使它们可见。充其量它每秒可以增加数百个。

一旦您的工作人员首次调用ReportProgress(),UI线程就会开始调度调用请求。它通过调用ProgressChanged事件处理程序来执行请求。问题是,一旦完成,还有另一个调用请求等待。它永远无法赶上并将调用队列清空。它会烧掉100%核心,除了调用ProgressChanged事件处理程序之外什么都不做。

停止正常工作,调度操作系统通知。其中包括输入,您会发现它不再响应鼠标点击和键盘输入。而绘画是一项低优先级的任务,只有在不需要其他任何事情时才能执行。

死锁或冻结,一旦工作线程完成,您就会看到它恢复活着状态。通常在工作完成几秒后,它仍然需要解决队列中的调用请求的积压。它可以在您关闭防火软管时使用Thread.Sleep()减慢工作速度,这样您就可以快速吞咽。

您需要多个解决方法来保持线程平衡:

  • 不要经常调用ReportProgress(),收集List<TreeNode^>^中的节点,直到你拥有一百个节点。记住线程安全性,您必须在ReportProgress()调用后创建一个新列表。
  • 在ProgressChanged事件处理程序中使用TreeView.Items.AddRange()方法,比一次添加一个方法更有效。
  • 避免TreeView做了太多工作来绘制节点,在RunWorkerCompleted事件处理程序中启动worker,EndUpdate()时调用其BeginUpdate()方法。你会错过&#34; live&#34;以这种方式更新,但这并不重要,用户也没有机会快速阅读它们。
  • 考虑到实时视图无论如何都不是很有趣,考虑不要在工作完成之前更新TreeView。在RunWorkerCompleted事件处理程序中执行此操作。