首页 > 解决方案 > 多次枚举返回任务的Linq Select查询

问题描述

我有一个 linq 查询,它返回一个任务对象并将其存储在IEnumerable. 由于某种原因,选择查询一直在枚举,直到任务开始或完成(我认为,很难调试)。

查询非常简单:

Context.RetrieveDataTasks = retrievableProducts.Select(product => Context.HostController.RetrieveProductDataFiles(product));

签名的地方RetrieveProductDataFiles是:

public Task RetrieveProductDataFiles(IProduct product)

在这种情况下,retrievableProducts 是 1 个产品的列表:

var retrievableProducts = products
    .Where(product => AFancyButIrrelevantClause)
    .ToList();

我不介意将代码重写为 foreach 循环,在该循环中我手动填充一个新列表以避免此问题,但我想了解为什么选择查询继续执行。我认为这与等待激活的任务有关,但我不知道为什么会导致这种情况。

编辑:

为了完整起见,我希望上面的代码与以下代码完全相同:

var retrievableDataTasks = new List<Task>();
foreach (var product in retrievableProducts)
{
    retrievableDataTasks.Add(Context.HostController.RetrieveProductDataFiles(product));
}
Context.RetrieveDataTasks = retrievableDataTasks;

虽然使用 a 的构造foreach完全符合我的期望:它填充了一个任务列表(在这种特定情况下是 1 个任务的列表),并且该任务执行一次。在使用Select查询构造时,一遍又一遍地启动相同的 1 任务。

我希望我提供的代码足够清楚,期待了解为什么选择查询的行为不同(以及如果可能的话,如何避免它发生)。

标签: c#task-parallel-library

解决方案


使用“ToList”会强制迭代器遍历所有集合,即使您认为您说“只需给我集合中的前两项”。如果该集合有 1000 个元素,您将迭代该集合,直到到达最后一个项目,它仍然会给您 2 个元素。

您可以通过使用 foreach 语句或 LINQ 查询来使用迭代器方法。foreach 循环的每次迭代都会调用迭代器方法。在迭代器方法中到达 yield return 语句时,返回表达式,并保留代码中的当前位置。下次调用迭代器函数时,将从该位置重新开始执行。

在您实例化添加到其中的列表的方法中,您需要稍微改进以使用收益返回,因此,不要分配不需要分配的数据。LINQ 方法是惰性求值的,这意味着在您尝试具体化结果(例如 ToList)之前不会为数据分配任何内存。当您在您的 LINQ 方法中时,您获得的唯一内存使用量是当前迭代,而不是您集合中的所有内容。

假设使用以下代码片段来帮助您。

private static IEnumerable<Product> GetMyProducts(IEnumerable<Product> products, bool AFancyButIrrelevantClause)
{
   foreach(var product in products)
   {
       if(AFancyButIrrelevantClause)
           yield return product;
    }
 }

或者直接在 LINQ 中更简洁:

 products.Where(product => AFancyButIrrelevantClause)

推荐阅读