C# 从单独线程上完成的某些工作更新UI元素时出现异常

C# 从单独线程上完成的某些工作更新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

我有一个UI应用程序,它简单地递归搜索特定的文件类型,并在列表框中显示结果。然而,它导致了典型的UI冻结问题,所以我尝试在单独的线程中进行搜索,并在UI线程中更新列表框。我有两个解决方案,其中一个是导致异常,而另一个是工作良好。问题是我不明白为什么解决方案1会抛出异常

解决方案#1
这会引发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());
}));