C# 带有异步代码的并行foreach循环不';由于未知原因,无法正确完成
我正试图重写一个C# 带有异步代码的并行foreach循环不';由于未知原因,无法正确完成,c#,task-parallel-library,parallel.foreach,C#,Task Parallel Library,Parallel.foreach,我正试图重写一个foreach循环以使用Parallel。foreach因为我需要处理的每个文档都可以作为一个单独的实体来处理,所以没有任何依赖关系 代码相当直截了当,如下所示: 查询数据库 阅读循环中的每个文档 对每个文档执行两次web调用,并将结果添加到文档中 将更新的文档添加到列表中 将列表导入数据库 由于网络延迟,web API调用是最慢的部分,所以我想并行处理它们以节省时间,所以我编写了这段代码 private async Task<List<String>>
foreach
循环以使用Parallel。foreach
因为我需要处理的每个文档都可以作为一个单独的实体来处理,所以没有任何依赖关系
代码相当直截了当,如下所示:
- 查询数据库
- 阅读循环中的每个文档
- 对每个文档执行两次web调用,并将结果添加到文档中
- 将更新的文档添加到列表中
- 将列表导入数据库
private async Task<List<String>> FetchDocumentsAndBuildList(string brand)
{
using (var client = new DocumentClient(new Uri(cosmosDBEndpointUrl), cosmosDBPrimaryKey))
{
List<string> formattedList = new List<string>();
FeedOptions queryOptions = new FeedOptions
{
MaxItemCount = -1,
PartitionKey = new PartitionKey(brand)
};
var query = client.CreateDocumentQuery<Document>(UriFactory.CreateDocumentCollectionUri(cosmosDBName, cosmosDBCollectionNameRawData), $"SELECT TOP 2 * from c where c.brand = '{brand}'", queryOptions).AsDocumentQuery();
while(query.HasMoreResults)
{
var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 10 };
Parallel.ForEach(await query.ExecuteNextAsync<Document>(), options, async singleDocument =>
{
JObject originalData = singleDocument.GetPropertyValue<JObject>("BasicData");
if (originalData != null)
{
var artNo = originalData.GetValue("artno");
if (artNo != null)
{
string strArtNo = artNo.ToString();
string productNumber = strArtNo.Substring(0, 7);
string colorNumber = strArtNo.Substring(7, 3);
string HmGoeUrl = $"https://xxx,xom/Online/{strArtNo}/en";
string sisApiUrl = $"https:/yyy.com/{productNumber}/{colorNumber}?&maxnumberofstores=10&brand=000&channel=02";
string HttpFetchMethod = "GET";
JObject detailedDataResponse = await DataFetcherAsync(HmGoeUrl, HttpFetchMethod);
JObject inventoryData = await DataFetcherAsync(sisApiUrl, HttpFetchMethod);
if (detailedDataResponse != null)
{
JObject productList = (JObject)detailedDataResponse["product"];
if (productList != null)
{
var selectedIndex = productList["articlesList"].Select((x, index) => new { code = x.Value<string>("code"), Node = x, Index = index })
.Single(x => x.code == strArtNo)
.Index;
detailedDataResponse = (JObject)productList["articlesList"][selectedIndex];
}
}
singleDocument.SetPropertyValue("DetailedData", detailedDataResponse);
singleDocument.SetPropertyValue("InventoryData", inventoryData);
singleDocument.SetPropertyValue("consumer", "NWS");
}
}
formattedList.Add(Newtonsoft.Json.JsonConvert.SerializeObject(singleDocument));
});
//foreach (Document singleDocument in await query.ExecuteNextAsync<Document>())
//{
// JObject originalData = singleDocument.GetPropertyValue<JObject>("BasicData");
// if(originalData != null)
// {
// var artNo = originalData.GetValue("artno");
// if(artNo != null)
// {
// string strArtNo = artNo.ToString();
// string productNumber = strArtNo.Substring(0, 7);
// string colorNumber = strArtNo.Substring(7, 3);
// string HmGoeUrl = $"https:/xxx.xom/Online/{strArtNo}/en";
// string sisApiUrl = $"https://yyy.xom&maxnumberofstores=10&brand=000&channel=02";
// string HttpFetchMethod = "GET";
// JObject detailedDataResponse = await DataFetcherAsync(HmGoeUrl, HttpFetchMethod);
// JObject inventoryData = await DataFetcherAsync(sisApiUrl, HttpFetchMethod);
// if(detailedDataResponse != null)
// {
// JObject productList = (JObject)detailedDataResponse["product"];
// if(productList != null)
// {
// var selectedIndex = productList["articlesList"].Select((x, index) => new { code = x.Value<string>("code"), Node = x, Index = index })
// .Single(x => x.code == strArtNo)
// .Index;
// detailedDataResponse = (JObject)productList["articlesList"][selectedIndex];
// }
// }
// singleDocument.SetPropertyValue("DetailedData", detailedDataResponse);
// singleDocument.SetPropertyValue("InventoryData", inventoryData);
// singleDocument.SetPropertyValue("consumer", "NWS");
// }
// }
// formattedList.Add(Newtonsoft.Json.JsonConvert.SerializeObject(singleDocument));
//}
}
return formattedList;
}
}
这里的问题是,
Parallel.ForEach
不理解在ForEach
被认为完成之前,需要等待对lambda的每个返回任务的调用
因此,在函数退出之前不会调用wait之后的continuation,这就是formattedList
中没有元素的原因
您可以通过以下代码示例轻松证明这一点:
Parallel.ForEach(Enumerable.Range(0, 100), async singleDocument => await Task.Delay(9999));
Console.WriteLine("Done!");
完成
将几乎立即打印
对于I/O绑定的并行性,您可以使用Task.whalll
来并行化异步webscraping调用
var myDocuments = await query.ExecuteNextAsync<Document>();
var myScrapingTasks = myDocuments.Select(async singleDocument =>
{
// ... all of your web scraping code here
// return the amended (mutated) document
return JsonConvert.SerializeObject(singleDocument);
});
var results = await Task.WhenAll(myScrapingTasks);
formattedList.AddRange(results);
var myDocuments=await query.ExecuteNextAsync();
var myScrapingTasks=myDocuments.Select(异步单文档=>
{
//…所有的网页抓取代码都在这里
//返回经修改(修改)的文档
返回JsonConvert.SerializeObject(singleDocument);
});
var results=等待任务.WhenAll(myScrapingTasks);
formattedList.AddRange(结果);
w、 r.tMaxDegreeOfParallelism
,如果您发现需要限制并发刮取调用的数量,最简单的方法是一次处理较小的块-选择(x,i)
过载和GroupBy
创造奇迹。您是否尝试过将列表
替换为ConcurrentBag
?据我所知,列表不是为并发代码设计的。@RobinB你在这方面打败了我;)它仍然失败,就像在循环完成检索数据并将其添加到包之前返回空包一样?我在袋子后面断开。加上,我可以看到循环每次迭代的计数都会增加,但是代码没有停止,它已经将空包返回给调用方method@StuartLC是的,我正在尝试将其更改为TPL陡峭的学习曲线,但是使用并行
类进行异步工作似乎是一个常见的错误。它不理解异步委托,这意味着它不接受返回Task
s的lambda。因此,当您向它提供async()=>
lambda时,将创建一个async void
。异步void本身就是一个问题。他们不能等待,他们的例外情况也无法处理。不幸的是,对于异步工作,没有类似于并行的类。在非常基本的任务和超级强大的TPL数据流库之间,我们没有任何东西。TPL数据流库可以做任何你能想象到的事情,但有学习曲线。
var myDocuments = await query.ExecuteNextAsync<Document>();
var myScrapingTasks = myDocuments.Select(async singleDocument =>
{
// ... all of your web scraping code here
// return the amended (mutated) document
return JsonConvert.SerializeObject(singleDocument);
});
var results = await Task.WhenAll(myScrapingTasks);
formattedList.AddRange(results);