为什么我的 C++/CLI 应用程序没有响应,即使我使用了 BackgroundWorker?
Why is my C++/CLI app unresponsive even when I've use a BackgroundWorker?
我只是想学习一些 Windows 表单内容以供参考,使用 C++/CLI。我使用默认的 Windows Forms 选项在 VS 2010 中创建了一个名为 LibraryScan 的项目,并使用一些控件修改了表单。我所做的所有代码更改都在 Form1.h 中(见下文)。我添加了整个 Form1.h 因为我想如果你只是创建一个普通的 VS 2010 C++/CLI Windows Forms 应用程序你可以用下面的内容替换自动生成的 Form1.h (尽管需要考虑图像列表;项目 [0] 是通用文档图标,[1] 是关闭的文件夹,[2] 是打开的文件夹)。
基本上你使用"Browse..."按钮到select一个文件夹,然后按"Scan"按钮,目的是它会递归通过根文件夹及其子文件夹来查找里面的所有文件。每个文件的名称被添加到一个多行的TextBox 中,树结构在一个TreeView 中生成。
我遇到的问题是,没有这条线:
System::Threading::Thread::Sleep(1);
在 listFolder() 函数中,UI 在扫描文件夹时有点反应迟钝。 TextBox 更新正常,但 TreeView 直到扫描完成才显示,并且在扫描时,您无法调整应用程序大小或移动应用程序 window。 Sleep(1) 进去没问题,虽然有点慢!
正如我所说,我是 Windows Forms 的新手,但对 MFC 有一些经验(尽管我 27 年以上的软件开发经验中的大部分都是嵌入式的,所以..)包括使用事件泵来尝试解决这类问题。然而,到目前为止我所做的阅读似乎表明 BackgroundWorker class 和 RunWorkerAsync()/ReportProgress() 等是 C++/CLI/Windows 形式的方式,但大多数问题和示例都在C# 和我使用 BackgroundWorker 搜索无响应的 GUI 的所有结果都以我看不到的解决方案与我正在做的有很大不同!
感谢任何帮助。
#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)
{
}
};
};
}
您的程序存在第 3 常见的线程错误。数字 1 和 2 是线程竞争和死锁,BackgroundWorker 非常擅长帮助您避免那些讨厌的问题。但它并不能帮助您避免第 3 种情况,即 消防水带错误。这里的心理形象是试图从 运行 宁消防水龙带喝水,无论你如何努力吞咽,你都无法避免溢出水。
这里的水就是worker生产出来的TreeNodes。您的文件系统速度很快,可以非常高的速度将它们吐出。特别是第二次 运行 你的程序和所有文件数据都来自文件系统缓存。每秒数万。
口是你程序中的UI线程,它需要将这些节点添加到TreeView中并生成一个paint使它们可见。充其量它可以每秒添加数百个。
一旦您的工作人员首次调用 ReportProgress(),UI 线程就会开始分派调用请求。它通过调用您的 ProgressChanged 事件处理程序来执行请求。问题是,一旦完成,就会有另一个调用请求在等待。它永远无法赶上并清空调用队列。它消耗了 100% 的核心,除了调用您的 ProgressChanged 事件处理程序之外什么都不做。
并停止执行其正常职责,即发送操作系统通知。其中包括输入,您会看到它不再响应鼠标点击和键盘输入。还有绘画,一个低优先级的任务,只有在没有其他事情需要做的时候才会执行。
它没有死锁或冻结,您会看到它在工作线程完成后立即恢复正常。通常在 worker 完成几秒钟后,它仍然需要处理队列中积压的调用请求。当你关掉消防水带时,它会起作用,用 Thread.Sleep() 减慢工人的速度,这样你就可以足够快地吞咽。
您需要不止一种解决方法来保持线程平衡:
- 不要如此频繁地调用 ReportProgress(),在
List<TreeNode^>^
中收集节点,直到有一百个节点为止。请牢记线程安全,您必须在 ReportProgress() 调用后创建一个新列表。
- 在您的 ProgressChanged 事件处理程序中使用 TreeView.Items.AddRange() 方法,这比一次添加一个要高效得多。
- 避免 TreeView 做太多工作来绘制节点,在启动工作程序时调用其 BeginUpdate() 方法,在 RunWorkerCompleted 事件处理程序中调用 EndUpdate()。您会以这种方式错过 "live" 更新,但这无关紧要,无论如何,用户没有任何机会快速阅读它们。
- 考虑到工作人员完成之前根本不更新 TreeView,因为实时视图无论如何都不是很有趣。在 RunWorkerCompleted 事件处理程序中执行此操作。
我只是想学习一些 Windows 表单内容以供参考,使用 C++/CLI。我使用默认的 Windows Forms 选项在 VS 2010 中创建了一个名为 LibraryScan 的项目,并使用一些控件修改了表单。我所做的所有代码更改都在 Form1.h 中(见下文)。我添加了整个 Form1.h 因为我想如果你只是创建一个普通的 VS 2010 C++/CLI Windows Forms 应用程序你可以用下面的内容替换自动生成的 Form1.h (尽管需要考虑图像列表;项目 [0] 是通用文档图标,[1] 是关闭的文件夹,[2] 是打开的文件夹)。
基本上你使用"Browse..."按钮到select一个文件夹,然后按"Scan"按钮,目的是它会递归通过根文件夹及其子文件夹来查找里面的所有文件。每个文件的名称被添加到一个多行的TextBox 中,树结构在一个TreeView 中生成。
我遇到的问题是,没有这条线:
System::Threading::Thread::Sleep(1);
在 listFolder() 函数中,UI 在扫描文件夹时有点反应迟钝。 TextBox 更新正常,但 TreeView 直到扫描完成才显示,并且在扫描时,您无法调整应用程序大小或移动应用程序 window。 Sleep(1) 进去没问题,虽然有点慢!
正如我所说,我是 Windows Forms 的新手,但对 MFC 有一些经验(尽管我 27 年以上的软件开发经验中的大部分都是嵌入式的,所以..)包括使用事件泵来尝试解决这类问题。然而,到目前为止我所做的阅读似乎表明 BackgroundWorker class 和 RunWorkerAsync()/ReportProgress() 等是 C++/CLI/Windows 形式的方式,但大多数问题和示例都在C# 和我使用 BackgroundWorker 搜索无响应的 GUI 的所有结果都以我看不到的解决方案与我正在做的有很大不同!
感谢任何帮助。
#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)
{
}
};
};
}
您的程序存在第 3 常见的线程错误。数字 1 和 2 是线程竞争和死锁,BackgroundWorker 非常擅长帮助您避免那些讨厌的问题。但它并不能帮助您避免第 3 种情况,即 消防水带错误。这里的心理形象是试图从 运行 宁消防水龙带喝水,无论你如何努力吞咽,你都无法避免溢出水。
这里的水就是worker生产出来的TreeNodes。您的文件系统速度很快,可以非常高的速度将它们吐出。特别是第二次 运行 你的程序和所有文件数据都来自文件系统缓存。每秒数万。
口是你程序中的UI线程,它需要将这些节点添加到TreeView中并生成一个paint使它们可见。充其量它可以每秒添加数百个。
一旦您的工作人员首次调用 ReportProgress(),UI 线程就会开始分派调用请求。它通过调用您的 ProgressChanged 事件处理程序来执行请求。问题是,一旦完成,就会有另一个调用请求在等待。它永远无法赶上并清空调用队列。它消耗了 100% 的核心,除了调用您的 ProgressChanged 事件处理程序之外什么都不做。
并停止执行其正常职责,即发送操作系统通知。其中包括输入,您会看到它不再响应鼠标点击和键盘输入。还有绘画,一个低优先级的任务,只有在没有其他事情需要做的时候才会执行。
它没有死锁或冻结,您会看到它在工作线程完成后立即恢复正常。通常在 worker 完成几秒钟后,它仍然需要处理队列中积压的调用请求。当你关掉消防水带时,它会起作用,用 Thread.Sleep() 减慢工人的速度,这样你就可以足够快地吞咽。
您需要不止一种解决方法来保持线程平衡:
- 不要如此频繁地调用 ReportProgress(),在
List<TreeNode^>^
中收集节点,直到有一百个节点为止。请牢记线程安全,您必须在 ReportProgress() 调用后创建一个新列表。 - 在您的 ProgressChanged 事件处理程序中使用 TreeView.Items.AddRange() 方法,这比一次添加一个要高效得多。
- 避免 TreeView 做太多工作来绘制节点,在启动工作程序时调用其 BeginUpdate() 方法,在 RunWorkerCompleted 事件处理程序中调用 EndUpdate()。您会以这种方式错过 "live" 更新,但这无关紧要,无论如何,用户没有任何机会快速阅读它们。
- 考虑到工作人员完成之前根本不更新 TreeView,因为实时视图无论如何都不是很有趣。在 RunWorkerCompleted 事件处理程序中执行此操作。