异步递归方法

本文关键字:递归方法 异步 | 更新日期: 2023-09-27 18:17:53

好吧...所以我正在转换我的一些简单使用的应用程序,以停止使用后台工作者系统将事情转换为标准异步。我使用异步从头开始构建了一个 WPF 应用程序,它运行得非常出色,所以我想转换其余部分以执行相同的操作(只是让我更容易阅读代码(。在这种情况下,我使用一种方法来清除目录,然后再从存储构建的机器(编译和存放它们的位置(复制文件和目录。我在使用"Empty"方法时遇到问题,我必须递归才能正常运行。这是目前的方法(有些事情是错误的(:

public static Task Empty(string targetDir)
    {
        return Task.Run(() =>
            {
                foreach (var directory in Directory.GetDirectories(targetDir))
                {
                    Empty(directory);
                    string[] filelist2 = Directory.GetFiles(directory);
                    foreach (string files in filelist2)
                    {
                        File.SetAttributes(files, FileAttributes.Normal);
                        File.Delete(files);
                    }
                    if (!Directory.EnumerateFileSystemEntries(directory).Any())
                    {
                        Directory.Delete(directory, false);
                    }
                }
                string[] filelist = Directory.GetFiles(targetDir);
                foreach (string files in filelist)
                {
                    File.SetAttributes(files, FileAttributes.Normal);
                    File.Delete(files);
                }
            });
    }

现在,它的作用是删除任何文件和子目录。它使用后台工作者工作(以前没有任务或任何东西(,但尝试在任务中运行最终会弹出一个关于无法找到文件的异常。我的猜测是这与线程有关,但我似乎无法弄清楚是什么。

任何想法可能导致问题?当它尝试对文件设置属性时它会失败(不是每次都同一个文件......似乎一旦它被递归循环多次,它就无法更改文件属性(。

异步递归方法

等待递归调用

public static Task Empty(string targetDir)
{
    return Task.Run(async () =>
        {
            foreach (var directory in Directory.GetDirectories(targetDir))
            {
                await Empty(directory);
                string[] filelist2 = Directory.GetFiles(directory);
                foreach (string files in filelist2)
                {
                    File.SetAttributes(files, FileAttributes.Normal);
                    File.Delete(files);
                }
                if (!Directory.EnumerateFileSystemEntries(directory).Any())
                {
                    Directory.Delete(directory, false);
                }
            }
            string[] filelist = Directory.GetFiles(targetDir);
            foreach (string files in filelist)
            {
                File.SetAttributes(files, FileAttributes.Normal);
                File.Delete(files);
            }
        });
}

编辑这里也有很多需要改进的地方。您正在删除文件两次。要解决此问题,您可以减少代码,例如

public static Task<bool> Empty(string targetDir)
{
    return Task.Run(async () =>
    {
        foreach (var directory in Directory.GetDirectories(targetDir))
        {
            if (await Empty(directory))
                Directory.Delete(directory, false);
        }
        var retval = true;
        foreach (string file in Directory.GetFiles(targetDir))
        {
            try
            {
                File.SetAttributes(file, FileAttributes.Normal);
                File.Delete(file);
            }
            catch(Exception ex)
            {
                // something went wrong: log ex
                retval = false;
            }
        }
        return retval;
    });
}

但这仍然不是真正的性能,因为您仍在等待递归调用返回。正如@Servy建议的那样,将创建许多无用的任务。让我向您展示一种仅通过一项任务完成此操作的方法。我们定义了一个同步函数:

public static bool Empty(string targetDir)
{
    foreach (var directory in Directory.GetDirectories(targetDir))
    {
        if (Empty(directory))
            Directory.Delete(directory, false);
    }
    var retval = true;
    foreach (string file in Directory.GetFiles(targetDir))
    {
        try
        {
            File.SetAttributes(file, FileAttributes.Normal);
            File.Delete(file);
        }
        catch(Exception ex)
        {
            // something went wrong: log ex
            retval = false;
        }
    }
    return retval;    
}

现在我们定义它的异步版本:

public static Task<bool> EmptyAsync(string targetDir)
{
    return Task.Run(() => this.Empty(targetDir));
}

这可能具有与每次回避调用创建任务相同/更好的性能。

看起来您要删除相同的文件两次,并且由于您在不同的线程中执行此操作,每个线程在开始删除之前枚举完整列表,因此您最终将尝试删除一个线程中的文件,而另一个线程中已经删除了该文件。

考虑文件:

/

a/1

/

a/2

现在考虑在文件夹/上运行代码。首先,您将递归到/a,这将(在单独的线程中(删除方法底部循环中的文件/a/1 和/a/2。同时,您将在方法顶部的循环中枚举文件/a/1 和/a/2。其中一个将在另一个之前发生,因此您将从其中一个或另一个获得FileNotFound。