阅读本文后: Nesting await in Parallel.ForEach
我尝试执行以下操作:
private static async void Solution3UsingLinq()
{
var ids = new List<string>() { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
var customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var id = await repo.getCustomer(i);
Console.WriteLine(id);
});
}
出于某种原因,这不起作用......我不明白为什么,我认为存在僵局,但我不确定......
答案 0 :(得分:6)
因此,在您的方法结束时,customerTasks
包含IEnumerable<Task>
尚未枚举。 Select
中的所有代码都没有运行。
在创建这样的任务时,立即实现序列可能更安全,以降低双重枚举的风险(并偶然创建第二批任务)。您可以通过调用序列上的ToList
来执行此操作。
所以:
var customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var id = await repo.getCustomer(i); //consider changing to GetCustomerAsync
Console.WriteLine(id);
}).ToList();
现在......如何处理您的任务列表?你需要等待他们全部完成......
您可以使用Task.WhenAll
执行此操作:
await Task.WhenAll(customerTasks);
您可以通过async
语句中的Select
代理实际返回一个值来更进一步,因此最终得到IEnumerable<Task<Customer>>
。
然后您可以使用different overload of Task.WhenAll
:
IEnumerable<Task<Customer>> customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var c = await repo.getCustomer(i); //consider changing to GetCustomerAsync
return c;
}).ToList();
Customer[] customers = await Task.WhenAll(customerTasks); //look... all the customers
当然,可能有更有效的方法可以一次性获得几个客户,但那可能是另一个问题。
如果相反,您希望按顺序执行异步任务:
var customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var id = await repo.getCustomer(i); //consider changing to GetCustomerAsync
Console.WriteLine(id);
});
foreach(var task in customerTasks) //items in sequence will be materialized one-by-one
{
await task;
}
答案 1 :(得分:2)
<强>增加:强>
LINQ的时候似乎有些混乱 实际执行的语句,特别是 Where 语句 我创建了一个小程序来显示 实际上访问了源数据 本答案结尾处的结果
加法结束
您必须了解大多数LINQ函数的惰性。
Lazy LINQ函数只会更改Enumerator
IEnumerable.GetEnumerator()
,foreach
将在您开始枚举时返回。因此,只要您调用惰性LINQ函数,查询就不会被执行。
只有在您开始枚举时,才会执行查询。调用ToList()
时启动枚举,或者Any()
,FirstOrDefault()
,Max()
,var query = toDoLists
.Where(todo => todo.Person == me)
.GroupBy(todo => todo.Priority)
.Select(todoGroup => new
{
Priority = todoGroup.Key,
Hours = todoGroup.Select(todo => todo.ExpectedWorkTime).Sum(),
}
.OrderByDescending(work => work.Priority)
.ThenBy(work => work.WorkCount);
等非等级LINQ函数启动枚举
在每个LINQ函数的注释部分中描述了该函数是否是惰性函数。您还可以通过检查返回值来查看函数是否是惰性的。如果它返回IEnumerable&lt; ...&gt; (或IQueryable)LINQ尚未枚举。
这种懒惰的好处是,只要你只使用惰性函数,改变LINQ表达式并不费时。只有在使用非惰性函数时,您才必须了解其影响。
例如,如果获取序列的第一个元素需要很长时间来计算,由于订购,分组,数据库查询等,请确保您不要再开始枚举一次(=不要... t不止一次地对同一序列使用非惰性函数)
不要在家里这样做:
假设您有以下查询
todoLists
此查询仅包含惰性LINQ函数。在所有这些陈述之后,if (query.Any()) // do grouping, summing, ordering
{
var highestOnTodoList = query.First(); // do all work again
Process(highestOnTodoList);
}
else
{ // nothing to do
GoFishing();
}
尚未被访问。
但是,只要获得结果序列的第一个元素,就必须访问所有元素(可能不止一次)以按优先级对它们进行分组,计算所涉及的工作小时总数并按降序优先级对它们进行排序。
Any()的情况,以及First()的情况:
var highestOnToDoList = query.FirstOrDefault(); // do grouping / summing/ ordering
if (highestOnTioDoList != null)
etc.
在这种情况下,最好使用正确的功能:
Enumerable.Select
回到您的问题
IEnumerable
语句只为您创建了一个ICustomerRepo repo = new CustomerRepo();
IEnumerable<Task<CustomerRepo>> query = ids.Select(id => repo.getCustomer(i));
foreach (var task in query)
{
id = await task;
Console.WriteLine(id);
}
对象。你忘了列举它了。
除此之外,您还多次构建了CustomerRepo。是那个意图吗?
IEnumerable<int> GetNumbers()
{
for (int i=0; i<10; ++i)
{
yield return i;
}
}
添加:LINQ语句何时执行?
我创建了一个小程序来测试执行LINQ语句的时间,特别是当执行Where时。
返回IEnumerable的函数:
public static void Main()
{
IEnumerable<int> number = GetNumbers();
IEnumerable<int> smallNumbers = numbers.Where(number => number < 3);
IEnumerator<int> smallEnumerator = smallNumbers.GetEnumerator();
bool smallNumberAvailable = smallEnumerator.MoveNext();
while (smallNumberAvailable)
{
int smallNumber = smallEnumerator.Current;
Console.WriteLine(smallNumber);
smallNumberAvailable = smallEnumerator.MoveNext();
}
}
使用旧式Enumerator
使用此枚举的程序if (smallNumbers.Any())
{
int x = smallNumbers.First();
Console.WriteLine(x);
}
在调试期间,我可以看到第一次在调用MoveNext()时第一次执行GetNumbers。执行GetNumbers()直到第一个yield return语句。
每次调用MoveNext()时,都会执行yield return之后的语句,直到执行下一个yield return。
更改代码以便使用foreach,Any(),FirstOrDefault(),ToDictionary等访问枚举器,这表明对这些函数的调用是实际访问原始源的时间。
'222', '1', 'R', '219', '-', 'MTG 8900 LO Level Alarm', '1', 'int16', 'N'
'223', '1', 'R', '220', '-', 'MTG 8900 LL Level Alarm', '1', 'int16', 'N'
'224', '1', 'R', '221', '-', 'MTG 8900 HI Level Alarm', '1', 'int16', 'N'
'225', '1', 'R', '222', '-', 'MTG 8900 HH Level Alarm', '1', 'int16', 'N'
'226', '1', 'R', '223', '-', 'MTG 8900 Addresses', '1', 'int16', 'N'
'227', '1', 'R', '224', '-', 'MTG 8900 Alarm Status', '1', 'int16', 'N'
'228', '1', 'R', '225', '-', 'MTG 8900 Internal Guage Temp', '1', 'int16',
'N'
'229', '1', 'R', '226', '-', 'MTG 8900 RDU Display', '1', 'int16', 'N'
'230', '1', 'R', '227', '-', 'MTG 8900 Returned Signal Strength', '1',
'int16', 'N'
'231', '1', 'R', '228', '-', 'MTG 8900 Inputs', '1', 'int16', 'N'
'232', '1', 'R', '229', '-', 'MTG 8900 Outputs', '1', 'int16', 'N'
'233', '1', 'R', '230', '-', 'MTG 8900 Software Rev', '1', 'int16', 'N'
'234', '1', 'R', '231', '-', 'MTG 8900 Guage Type ', '1', 'int16', 'N'
'235', '1', 'R', '232', '-', 'MTG 8900 Tank ID', '1', 'int16', 'N'
'236', '1', 'R', '233', '-', 'MTG 8900 Max Wkg Level', '1', 'int16', 'N'
'237', '1', 'R', '234', '-', 'MTG 8900 Current Display', '1', 'int16', 'N'
'238', '1', 'R', '235', '-', 'MTG 8900 Temp Type', '1', 'int16', 'N'
'239', '1', 'R', '236', '-', 'MTG 8900 No of Bulbs', '1', 'int16', 'N'
'240', '1', 'R', '237', '-', 'MTG 8900 Sensitivity', '1', 'int16', 'N'
'241', '1', 'R', '238', '-', 'MTG 8900 Raw Data', '1', 'int16', 'N'
调试显示原始源从头开始枚举两次。所以确实这样做是不明智的,特别是如果你需要做很多事来计算第一个元素(GroupBy, OrderBy,数据库访问等)