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

时间:2014-02-10 16:19:29

标签: c# winforms delegates thread-safety .net-2.0

我有一个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会抛出异常。

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。