异步递归方法

时间:2014-08-13 12:41:22

标签: c# asynchronous recursion task-parallel-library

好吧......所以我正在转换我的一些简单用途应用程序,以停止使用后台工作系统处理标准异步。我使用async从头开始构建了一个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);
                }
            });
    }

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

任何可能导致问题的想法?当它尝试在一个文件上设置SetAttributes时失败(每次都不是同一个文件......只是一旦它递归循环很多次,它无法改变文件属性)。

2 个答案:

答案 0 :(得分:3)

看起来你正在删除相同的文件两次,并且因为你在不同的线程中进行它,每个线程在开始删除之前枚举完整列表,你最终会尝试删除一个已经存在的线程中的文件在另一个中被删除。

考虑文件:

/一个/ 1

/ A / 2

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

答案 1 :(得分:2)

等待递归调用

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));
}

这可能与每次recusrsion调用创建Task相同/更好的性能。