我有一个UI应用程序,它只是递归搜索特定的文件类型并在列表框中显示结果。但是它导致了经典的UI冻结问题,因此我尝试在单独的线程中进行搜索并在UI线程中更新列表框。我有两个解决方案,其中一个导致异常,而另一个解决方案很好。问题是我不明白为什么解决方案#1会抛出异常。
解决方案#1
抛出IndexOutOfRangeException:在调用this.BeginInvoke时,索引超出了数组的范围((Action)(()=> {this.lsResult.Items.Add(files [startingIndex] .ToString());}) );
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnSearch_Click(object sender, EventArgs e)
{
this.lsResult.Items.Clear();
this.Cursor = Cursors.WaitCursor;
new Thread(BeginSearch).Start();
this.Cursor = Cursors.Default;
}
private void BeginSearch()
{
string searchPath = this.textBox1.Text; // C:\*.txt
RecurseDirectory(searchPath);
}
private void RecurseDirectory(string searchPath)
{
string directory = Path.GetDirectoryName(searchPath);
string search = Path.GetFileName(searchPath);
if (directory == null || search == null)
{
return;
}
string[] files = Directory.GetFiles(directory, search);
int startingIndex = 0;
while (startingIndex < files.Length)
{
//IndexOutOfRangeException: Index was outside the bounds of the array.
this.BeginInvoke((Action)(() =>
{
this.lsResult.Items.Add(files[startingIndex].ToString());
}));
Interlocked.Increment(ref startingIndex);
}
string[] directories = Directory.GetDirectories(directory);
foreach (string d in directories)
{
RecurseDirectory(Path.Combine(d, search));
}
}
}
解决方案#2
这很有效。
public partial class Form1 : Form
{
private delegate void FileListDelegate(string[] files);
private FileListDelegate _FileListDelegate;
public Form1()
{
InitializeComponent();
_FileListDelegate = new FileListDelegate(ShowFileNames);
}
private void btnSearch_Click(object sender, EventArgs e)
{
this.lsResult.Items.Clear();
this.Cursor = Cursors.WaitCursor;
new Thread(BeginSearch).Start();
this.Cursor = Cursors.Default;
}
private void BeginSearch()
{
string searchPath = this.textBox1.Text;
RecurseDirectory(searchPath);
}
private void RecurseDirectory(string searchPath)
{
string directory = Path.GetDirectoryName(searchPath);
string search = Path.GetFileName(searchPath);
if (directory == null || search == null)
{
return;
}
string[] files = Directory.GetFiles(directory, search);
this.BeginInvoke(_FileListDelegate, new object[] { files });
string[] directories = Directory.GetDirectories(directory);
foreach (string d in directories)
{
RecurseDirectory(Path.Combine(d, search));
}
}
void ShowFileNames(string[] files)
{
foreach(string file in files)
this.lsResult.Items.Add(file.ToString());
}
}
这两个解决方案对我来说都非常相似,我不明白为什么#1会抛出异常。
答案 0 :(得分:1)
您正在关闭第一个解决方案中的变量startingIndex
。重要的是要意识到闭包关闭变量,而不是超过值。当委托最终执行时,该方法将使用startingIndex
的值,这可能是在遥远的未来的某个时刻,而不是现在使用该变量的值。你不断递增startIndex
,直到它被设置为超出数组末尾的值,因此当你在所有这些增量之后使用该变量结束执行代码时,你的索引会超出边界错误。
最小的代码更改是简单地获取循环中 的变量的副本,该变量永远不会改变。然后关闭那个变量。
int index = startingIndex;
this.BeginInvoke((Action)(() =>
{
this.lsResult.Items.Add(files[index].ToString());
}));
另请注意,您可以通过递归遍历文件系统来大大简化代码。只需使用Directory.GetFiles(searchPath, "*.*", SearchOption.AllDirectories)
而不是自己显式处理递归,然后您可以使用foreach
循环浏览这些文件,并将每个文件添加到UI。