哪个任务<;T>;扩展可以用来递归地等待其他任务

本文关键字:任务 递归 等待 其他 扩展 lt gt | 更新日期: 2023-09-27 18:30:05

我真的不确定有什么有效的方法可以做到这一点。我有文件,其中文件的内容指向其他文件,例如:

A
|-- B
|   |-- C
|   |-- D
|       |-- E
|
|-- F
    |-- C
G
|-- H
|   |-- I
|
|-- D
|   |-- E
|
|-- J

这种情况会持续到成千上万的文件中;幸运的是,依赖关系的深度非常浅,但为了论证起见,它可能是N级的,不可能有循环循环。我的目标是了解每个文件(展开)的整个依赖关系。例如:

  • A: (B,C,D,E,F)--注意"C"只列出一次
  • B: (C、D、E)
  • C: ()
  • D: (E)
  • E: ()
  • F: (C)
  • G: (D,E,H,I,J)
  • 等等

我首先创建了一些模型来跟踪这些信息:

public class FileData
{
    public string FilePath { get; set; }
    public ISet<FileInfo> DependentUpon { get; set; }
}

当然,我随后创建了一个List<FileData>来存储处理后的文件。同步扫描文件的内容以建立这个依赖树(然后将其压平)会花费太长时间,所以我探索了异步/等待,这有助于加快速度,但我希望在将其发布到生产环境之前使其更快。

我的async/await尝试要快得多,但仍然不够有效。

public async Task<ICollection<FileData>> ProcessAsync(IEnumerable<FileInfo> files)
{
    var mappings = new Dictionary<FileInfo, FileData>();
    foreach (var file in files)
    {
        // Static Method that constructs an instance of the class
        // and utilizes async I/O to read the file line-by-line
        // to build any first level dependencies.
        var info = await FileData.CreateAsync(file);
        // Update progress + Other Properties
        mappings.Add(file, info);
    }
    // Go through the list and recursively add to the dependencies
    foreach (var item in list)
    {
        foreach (var dependency in GetAllDependencies(item, mappings))
        {
           file.DependentUpon.Add(dependency);
        }
    }
}
IEnumerable<FileInfo> GetAllDependencies(FileData data, IDictionary<FileInfo, FileData> mappings)
{
    foreach (var file in info.DependentUpon)
    {
        yield return file;
        foreach (var child in GetAllDependencies(mappings[file], mappings))
        {
            yield return child;
        }
    }
}

当然,这在某种程度上是异步的,但当我试图获得层次结构(扁平化)时,它仍然非常同步和缓慢。我正在尝试重构该解决方案,以便利用分层搜索中的异步/等待功能更快地工作。到目前为止,我只有伪描述,我不知道这是否可能,也不知道如何正确实现:

创建一个FileInfoTask<FileData>的字典(这样我就不再等待类实例的构造了)。在扫描第一级DependentUpon的文件后,我找到了匹配的任务,并且只有在这些任务完成后才能继续我的当前任务。当然,这些任务有相同的说明,因此只有当它们的依赖关系完成时,它们才会被标记为已完成。我想同时开始所有任务。例如(只是一个例子,我无法预测什么任务何时完成):

  • 启动任务A
  • 启动任务B
  • 扫描文件A,取决于(B,F)
  • 启动任务C
  • 扫描文件B,取决于(C,D)
  • 任务A等待任务B和任务F完成
  • 启动任务D
  • 扫描文件C
  • 任务D等待任务E完成
  • 扫描文件E,取决于()
  • 任务E已完成
  • 任务D已完成
  • 任务C已完成
  • 任务B已完成
  • 启动任务J
  • 任务F已完成
  • 任务A已完成
  • 所有任务已完成

哪个任务<;T>;扩展可以用来递归地等待其他任务

考虑使用Task.WhenAll<>以同时等待(递归地)加载根项目的任务。您还可以推迟依赖项列表的扩展,这样可以减少函数的运行时间,并更有效地使用内存。

    public class FileData
    {
       public string FilePath { get; set; }
       public ISet<FileInfo> DependentUpon { get; set; }
       public IEnumerable<FileInfo> Dependencies {get; set;}
    }

新属性Dependencies提供了所有从属项的列表。DependentUpon现在只包含直接依赖项,不必更改。

    public async Task<ICollection<FileData>> ProcessAsync(IEnumerable<FileInfo> files)
    {
        var map = new Dictionary<FileInfo, Task<FileData>>();
        var tasks = files.Select(it => LoadFileDataAsync(it, map));
        return await Task.WhenAll(tasks);
    }
    static async Task<FileData> LoadFileDataAsync(FileInfo fileInfo, Dictionary<FileInfo, Task<FileData>> map)
    {
       // Load recursively FileData elements for all children 
       // storing the result in the map.
        Task<FileData> pendingTask;
        bool isNew;
        lock (map)
        {
            isNew = !map.TryGetValue(fileInfo, out pendingTask);
            if (isNew)
            {
                pendingTask = FileData.CreateAsync(fileInfo);
                map.Add(fileInfo, pendingTask);
            }
        }
        var data = await pendingTask;
        if (isNew)
        {
           // Assign an iterator traversing through the dependency graph
           // Note: parameters are captured by reference.
           data.Dependencies = ExpandDependencies(data.DependsUpon, map);
           if (data.DependsUpon.Count > 0)
           {
              // Recursively load child items
              var tasks = data.DependsUpon.Select(it => (Task)LoadFileDataAsync(it, map));
              await Task.WhenAll(tasks);
           }
        }
        return data;
    }

    static IEnumerable<FileInfo> ExpandDependencies(ISet<FileInfo> directDependencies, Dictionary<FileInfo, Task<FileData>> map)
    {
        if (directDependencies.Count == 0)
        {
            yield break;
        }
        // Depth-first graph traversal
        var visited = new HashSet<FileInfo>(map.Comparer); // check for duplicates
        var stack = new Stack<FileInfo>();
        foreach(var item in directDependencies)
        {
            stack.Push(item);
        }
        while(stack.Count > 0)
        {
            var info = stack.Pop();
            if (visited.Add(info))
            {
                yield return info;
                var data = map[info].Result;
                foreach (var child in data.DependsUpon)
                {
                    stack.Push(child);
                }
            }
        }
    }

工作代码samlple