并行.ForEach中嵌套等待(await)

228

在一个 Metro 应用中,我需要执行多个 WCF 调用。由于需要进行大量的调用,因此我需要在并行循环中执行它们。但是问题在于,在 WCF 调用完成之前并行循环就已经退出了。

你会如何重构代码以满足预期功能?

var ids = new List<string>() { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
var customers = new  System.Collections.Concurrent.BlockingCollection<Customer>();

Parallel.ForEach(ids, async i =>
{
    ICustomerRepo repo = new CustomerRepo();
    var cust = await repo.GetCustomer(i);
    customers.Add(cust);
});

foreach ( var customer in customers )
{
    Console.WriteLine(customer.ID);
}

Console.ReadKey();

2
我已经将这个问题标记为使用异步lambda的并行foreach的重复问题,尽管那个问题比这个问题新几个月,因为另一个问题包含一个已经得到大量赞同的答案,该答案推荐了当前解决此问题最佳的方法,即新的Parallel.ForEachAsync API。 - Theodor Zoulias
11个回答

-1

不使用TPL的简单本地方法:

int totalThreads = 0; int maxThreads = 3;

foreach (var item in YouList)
{
    while (totalThreads >= maxThreads) await Task.Delay(500);
    Interlocked.Increment(ref totalThreads);

    MyAsyncTask(item).ContinueWith((res) => Interlocked.Decrement(ref totalThreads));
}

你可以用下一个任务来检查这个解决方案:

async static Task MyAsyncTask(string item)
{
    await Task.Delay(2500);
    Console.WriteLine(item);
}

尝试不错,但是这种方法存在多个问题:未同步访问非volatile变量totalThreads。在循环中无效地等待条件的满足(引入延迟)。使用原始ContinueWith方法而没有指定TaskScheduler。如果MyAsyncTask同步抛出异常,则可能会泄漏fire-and-forget任务。这个功能令人惊讶地棘手,自己尝试第一次很难做到正确。 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接