在递归函数中使用 BeginInvoke 将节点添加到 BackgroundWorker 中的 TreeView

Using BeginInvoke in a recursive function adding nodes to a TreeView in a BackgroundWorker

我的代码具有扫描根目录并在 TreeView 中显示 txt 文件(例如)的功能:

private TreeNode DirectoryToTreeView(TreeNode parentNode, string path,
                                     string extension = ".txt")
{
    var result = new TreeNode(parentNode == null ? path : Path.GetFileName(path));
    foreach (var dir in Directory.GetDirectories(path))
    {
        TreeNode node = DirectoryToTreeView(result, dir);
        if (node.Nodes.Count > 0)
        {
            result.Nodes.Add(node);
        }
    }
    foreach (var file in Directory.GetFiles(path))
    {
        if (Path.GetExtension(file).ToLower() == extension.ToLower())
        {
            result.Nodes.Add(Path.GetFileName(file));
        }
    }
    return result;
}

这个函数应该像这样从按钮调用: treeView1.Nodes.Add(DirectoryToTreeView(null, @"C:\Users\Tomer\Desktop\a")); 它显然冻结了 UI。 我是新手,我在网上搜索了一下,似乎没有什么与我的问题相关,因为没有人使用递归函数,我不能简单地调用 BeginInvoke 整个函数,因为它没有任何效果。 我应该走什么路?也许更改函数以使用 while 循环,然后在 if 语句中调用 BeginInvoke?在内存中创建一个 TreeNode 对象来填充(可能太大)?

下面是一个异步 Task 方法的示例,用于使用给定文件类型的目录树填充 TreeNode。里面的CreateTree(...)是一个local function递归调用遍历目录

private async Task<TreeNode> CreateTreeAsync(string startDir, string fileExt)
{
    var di = new DirectoryInfo(startDir);
    var result = new TreeNode(di.Name);
    var searchPattern = $"*.{fileExt.TrimStart('.')}";

    return await Task.Run(() =>
    {
        void CreateTree(DirectoryInfo dirInfo, TreeNode node)
        {
            try
            {
                foreach (var fileInfo in dirInfo.EnumerateFiles(searchPattern))
                    node.Nodes.Add(fileInfo.Name);

                foreach (var subDir in dirInfo.EnumerateDirectories())
                {
                    try
                    {
                        // Optional to skip the branches with no files at any level.
                        if (!subDir.EnumerateFiles(searchPattren, 
                            SearchOption.AllDirectories).Any()) continue;

                        var newNode = new TreeNode(subDir.Name);
                        node.Nodes.Add(newNode);
                        CreateTree(subDir, newNode);
                    }
                    catch (Exception ex)
                    {
                        // Skip exceptions like UnauthorizedAccessException
                        // and continue...
                        Console.WriteLine(ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        CreateTree(di, result);
        return result;
    });
}

注意: 如果您在 .NET 5+/.NET Core 下,则不需要 try..catch 块来跳过无法访问的目录和文件。使用带有 EnumerationOptions 参数的 EnumerateXXX 方法重载。

现在您需要 async 来电者:

private async void someButton_Click(object sender, EventArgs e)
{        
    // Optional...                
    treeView1.Nodes.Clear();

    var dir = @"...";
    var ext = "txt";
    var node = await CreateTreeAsync(dir, ext);

    if (node.Nodes.Count == 0)
        MessageBox.Show($"No '{ext}' files were found.");
    else
    {
        treeView1.Nodes.Add(node);
        node.Expand();
    }
}

您可以将 DirectoryToTreeNode 方法转换为 asynchronous method, and offload any blocking I/O operation to the ThreadPool, by using the Task.Run 方法:

private async Task<TreeNode> DirectoryToTreeNodeAsync(string path,
    TreeNode parentNode = null)
{
    var node = new TreeNode(parentNode == null ? path : Path.GetFileName(path));
    string[] subdirectories = await Task.Run(() => Directory.GetDirectories(path));
    foreach (string dirPath in subdirectories)
    {
        TreeNode childNode = await DirectoryToTreeNodeAsync(dirPath, node);
        node.Nodes.Add(childNode);
    }
    string[] files = await Task.Run(() => Directory.GetFiles(path));
    foreach (string filePath in files)
    {
        node.Nodes.Add(Path.GetFileName(filePath));
    }
    return node;
}

请注意,运行 在 ThreadPool 上(在 Task.Run 委托内)时没有触及 UI 控件。所有 UI 控件都应被操作 exclusively by the UI thread

用法示例:

private async void Button1_Click(object sender, EventArgs e)
{
    Button1.Enabled = false;
    Cursor = Cursors.WaitCursor;
    try
    {
        TreeView1.Nodes.Clear();
        TreeView1.Nodes.Add(
            await DirectoryToTreeNodeAsync(@"C:\Users\Tomer\Desktop\a"));
    }
    finally
    {
        Cursor = Cursors.Default;
        Button1.Enabled = true;
    }
}