试图理解async / await和parallelism

时间:2015-06-24 06:16:29

标签: c# multithreading asynchronous parallel-processing

我正在编写一个可能需要很长时间才能执行导入的应用程序。 为了加快导入过程,我使用任务并行库实现了它,请参阅下面的代码:

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相同的输出吗?

如果没有,有人可以向我解释这里发生了什么,因为我不太了解它。

亲切的问候

1 个答案:

答案 0 :(得分:4)

您编写的ForEachAsync将按顺序执行任务。第一个任务完成后,第二个元素的处理才会开始。

在ForEachAsync中执行await body(item)时会发生的事情是ForEachAsync的执行被暂停,调用方法将继续执行var task = nodes.ForEachAsync之后的语句。 body完成后ForEachAsync将恢复并继续使用列表中的第二项进行预告。

与常规foreach的区别在于ForEachAsync返回一个任务,这意味着如果你没有等待它,那么调用ForEachAsync的方法将在ForEachAsync完成之前继续下一个语句。因此,您应该根据所需的行为,使用awaittask.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()并查看更改后的行为。

您可能还想查看此msdn article explaining async and await