[英]Using async/await inside a Select linq query
閱讀這篇文章后: 在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);
});
}
由於某種原因,這行不通...我不明白為什么,我認為這是一個僵局,但我不確定...
因此,在方法末尾, 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
來做到這Task.WhenAll
:
await Task.WhenAll(customerTasks);
通過在Select
語句中從async
委托中實際返回一個值,可以使這一步驟更進一步,最終得到IEnumerable<Task<Customer>>
。
然后,您可以使用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;
}
加成:
關於何時實際執行LINQ語句,尤其是Where語句,似乎有些混亂。
我創建了一個小程序,以顯示實際何時訪問源數據。此答案末尾的結果
加法結束
您必須了解大多數LINQ函數的惰性。
懶惰的LINQ功能只會更改Enumerator
該IEnumerable.GetEnumerator()
會當你開始枚舉返回。 因此,只要您調用惰性LINQ函數,查詢就不會執行。
僅當您開始枚舉時,才執行查詢。 當調用foreach
或非延遲LINQ函數(例如ToList()
, Any()
, FirstOrDefault()
, Max()
等Max()
,枚舉開始。
在每個LINQ函數的注釋部分中,描述了該函數是否是惰性的。 您還可以通過檢查返回值來查看函數是否是惰性的。 如果返回IEnumerable <...>(或IQueryable),則尚未枚舉LINQ。
關於這種惰性的好處是,只要您僅使用惰性函數,更改LINQ表達式就不會浪費時間。 僅當您使用非惰性函數時,您才需要注意其影響。
例如,如果由於順序,分組,數據庫查詢等原因,要提取序列的第一個元素需要很長時間才能計算出來,請確保不要再枚舉一次(=請勿將非惰性函數用於同一序列不止一次)
不要在家中這樣做:
假設您有以下查詢
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函數。 所有這些語句之后,尚未訪問todoLists
。
但是,一旦獲得結果序列的第一個元素,就必須訪問所有元素(可能不止一次)以按優先級對它們進行分組,計算涉及的工作時間總數並按降序對它們進行排序。
Any()和First()都是這種情況:
if (query.Any()) // do grouping, summing, ordering
{
var highestOnTodoList = query.First(); // do all work again
Process(highestOnTodoList);
}
else
{ // nothing to do
GoFishing();
}
在這種情況下,最好使用正確的功能:
var highestOnToDoList = query.FirstOrDefault(); // do grouping / summing/ ordering
if (highestOnTioDoList != null)
etc.
回到你的問題
Enumerable.Select
語句僅為您創建了IEnumerable
對象。 您忘記枚舉了。
此外,您還多次構造了CustomerRepo。 那是故意的嗎?
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);
}
另外:LINQ語句何時執行?
我創建了一個小程序來測試LINQ語句何時執行,尤其是執行Where時。
返回IEnumerable的函數:
IEnumerable<int> GetNumbers()
{
for (int i=0; i<10; ++i)
{
yield return i;
}
}
一個使用老式枚舉器使用此枚舉的程序
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();
}
}
在調試期間,我可以看到第一次調用MoveNext()時第一次執行GetNumbers。 GetNumbers()一直執行到第一個收益返回語句為止。
每次調用MoveNext()時,都會執行收益返回之后的語句,直到執行下一個收益返回為止。
更改代碼,使之可以使用foreach,Any(),FirstOrDefault(),ToDictionary等訪問枚舉數,這表明對這些函數的調用是實際訪問原始源的時間。
if (smallNumbers.Any())
{
int x = smallNumbers.First();
Console.WriteLine(x);
}
調試顯示原始源從頭開始兩次枚舉。 因此,確實這樣做是不明智的,特別是如果您需要做很多事情來計算第一個元素(GroupBy,OrderBy,數據庫訪問等)時,這是不明智的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.