C# 从单独线程上完成的某些工作更新UI元素时出现异常
我有一个UI应用程序,它简单地递归搜索特定的文件类型,并在列表框中显示结果。然而,它导致了典型的UI冻结问题,所以我尝试在单独的线程中进行搜索,并在UI线程中更新列表框。我有两个解决方案,其中一个是导致异常,而另一个是工作良好。问题是我不明白为什么解决方案1会抛出异常 解决方案#1C# 从单独线程上完成的某些工作更新UI元素时出现异常,c#,winforms,delegates,thread-safety,.net-2.0,C#,Winforms,Delegates,Thread Safety,.net 2.0,我有一个UI应用程序,它简单地递归搜索特定的文件类型,并在列表框中显示结果。然而,它导致了典型的UI冻结问题,所以我尝试在单独的线程中进行搜索,并在UI线程中更新列表框。我有两个解决方案,其中一个是导致异常,而另一个是工作良好。问题是我不明白为什么解决方案1会抛出异常 解决方案#1 这会引发IndexOutOfRangeException:在调用This.BeginInvoke((操作)(()=>{This.lsResult.Items.Add(files[startingIndex].ToSt
这会引发IndexOutOfRangeException:在调用This.BeginInvoke((操作)(()=>{This.lsResult.Items.Add(files[startingIndex].ToString();})时,索引超出了数组的边界
这两种解决方案在我看来都非常相同,我不明白为什么#1会引发异常。您正在关闭第一个解决方案中的变量
startingIndex
。重要的是要认识到闭包是在变量上关闭的,而不是在值上关闭的。当委托最终执行时,该方法将使用startingIndex
的值,该值可能在遥远的将来的某个时刻,而不是现在使用该变量的值。您不断递增startIndex
,直到它被设置为超过数组末尾的值,因此当您在所有这些递增之后使用该变量执行代码时,索引会出现越界错误
最小的代码更改就是在循环中复制一个永远不会更改的变量。然后关闭该变量
int index = startingIndex;
this.BeginInvoke((Action)(() =>
{
this.lsResult.Items.Add(files[index].ToString());
}));
还请注意,通过让文件系统递归遍历自身,可以大大简化代码。只需使用
Directory.GetFiles(searchPath,“**”,SearchOption.AllDirectories)
而不是自己显式处理递归,然后您就可以使用foreach
循环遍历这些文件,并将每个文件添加到UI中。谢谢。一个附带的问题是,我添加了一个取消按钮。在其事件处理程序中,我将布尔标志设置为true。但由于某些原因,当我尝试单击cancel时,我从未点击事件处理程序。考虑到我的处理是在单独的线程中进行的,单击按钮不应该被锁定,应该响应用户的单击,对吗?@PaulSnow您正在执行大量对UI线程的调用,这将使其非常繁忙,从而阻止其他UI操作的发生。您最有可能使用Invoke
,而不是BeginInvoke
,以防止一次计划多个UI操作。我正在执行BeginInvoke以在UI线程上执行UI更新。我怎样才能调用它呢?我是不是误解了什么here@PaulSnow您将BeginInvoke
替换为Invoke
,然后就完成了。如果没有,那么从非UI线程访问UI对象会出现异常。你为什么不花点时间看看这两种方法的文档,研究一下什么时候应该使用每种方法;它应该给你一个非常清楚的区别指示。
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());
}
}
int index = startingIndex;
this.BeginInvoke((Action)(() =>
{
this.lsResult.Items.Add(files[index].ToString());
}));