在Select linq查询中使用async / await

时间:2018-03-07 00:42:20

标签: c# linq asynchronous select async-await

阅读本文后: 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);

    });
}

出于某种原因,这不起作用......我不明白为什么,我认为存在僵局,但我不确定......

2 个答案:

答案 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,数据库访问等)