我正在编写一个可能需要很长时间才能执行导入的应用程序。 为了加快导入过程,我使用任务并行库实现了它,请参阅下面的代码:
var nodes = XmlReaderUtils.EnumerateAxis(reader, new[] { "Node", "ArticleGroup" });
Parallel.ForEach(nodes, element =>
{
// ToDo: Still write a method to process the "Node", which are "Classifications" here.
if (element.Name == "Node") { }
if (element.Name == "ArticleGroup") { new ArticleDataImporter(element).Import(); }
});
如果我正确理解了这个概念,它会创建尽可能多的线程来尽可能快地处理Parallel.ForEach中的代码(如果我错了,请纠正我)。
现在,我正在网上浏览并遇到了一些async / await blogpost,它的用途与我想的相同。
我为那个编写了一个扩展方法,它看起来如下:
public static async Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> body)
{
var exceptions = new List<Exception>();
foreach (var item in source)
{
try { await body(item); }
catch (Exception ex) { exceptions.Add(ex); }
}
if (exceptions.Any()) { throw new AggregateException(exceptions); }
}
现在,如果我按以下方式调用此扩展方法:
var task = nodes.ForEachAsync(async element =>
{
// ToDo: Still write a method to process the "Node", which are "Classifications" here.
if (element.Name == "Node") { }
if (element.Name == "ArticleGroup") { await new ArticleDataImporter(element).Import(); }
});
这会产生与使用TPL相同的输出吗?
如果没有,有人可以向我解释这里发生了什么,因为我不太了解它。
亲切的问候
答案 0 :(得分:4)
您编写的ForEachAsync将按顺序执行任务。第一个任务完成后,第二个元素的处理才会开始。
在ForEachAsync中执行await body(item)
时会发生的事情是ForEachAsync的执行被暂停,调用方法将继续执行var task = nodes.ForEachAsync
之后的语句。 body
完成后ForEachAsync
将恢复并继续使用列表中的第二项进行预告。
与常规foreach的区别在于ForEachAsync返回一个任务,这意味着如果你没有等待它,那么调用ForEachAsync的方法将在ForEachAsync完成之前继续下一个语句。因此,您应该根据所需的行为,使用await
或task.Wait()
在您的调用代码中的某个位置等待该任务。
实现并行foreach的方法可以这样做:
public static void ParallelForEach<T>(this IEnumerable<T> source, Func<T, Task> body)
{
List<Task> tasks = new List<Task>();
foreach (var item in source)
{
tasks.Add(body(item));
}
Task.WaitAll(tasks.ToArray());
}
这将并行运行您的任务,它将等待所有任务在返回之前完成。
通过运行此代码来查看行为差异:
List<int> ints = new List<int> {3, 2, 1};
ints.ForEachAsync(async i =>
{
Console.WriteLine("Task async {0} starting", i);
await Task.Delay(i*1000);
Console.WriteLine("Task async {0} done", i);
}
).Wait();
ints.ParallelForEach(async i =>
{
Console.WriteLine("Task parallel {0} starting", i);
await Task.Delay(i*1000);
Console.WriteLine("Task parallel {0} done", i);
});
还尝试从第一个ints.ForEachAsync
调用中删除.Wait()并查看更改后的行为。