哪个任务<;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;
}
}
}
当然,这在某种程度上是异步的,但当我试图获得层次结构(扁平化)时,它仍然非常同步和缓慢。我正在尝试重构该解决方案,以便利用分层搜索中的异步/等待功能更快地工作。到目前为止,我只有伪描述,我不知道这是否可能,也不知道如何正确实现:
创建一个FileInfo
和Task<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已完成
- 所有任务已完成
考虑使用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