[英]Parallel.ForEach blocking calling method
我遇到了Parallel.ForEach
的問題。 我編寫了簡單的應用程序,將要下載的文件名添加到隊列中,然后使用 while 循環遍歷隊列,一次下載一個文件,然后在下載文件后,調用另一個異步方法從下載的文件中創建 object memoryStream
。 該方法的返回結果不等待,直接丟棄,立即開始下一次下載。 如果我在 object 創建中使用簡單的foreach
,一切正常 - 在繼續下載的同時創建對象。 但是,如果我想加快 object 創建過程並使用Parallel.ForEach
它會停止下載過程,直到創建 object。 UI 完全響應,但它不會下載下一個 object。 我不明白為什么會發生這種情況 - Parallel.ForEach
在await Task.Run()
內部,並且據我對異步編程的有限了解,這應該可以解決問題。 誰能幫我理解為什么它會阻止第一種方法以及如何避免它?
這是一個小樣本:
public async Task DownloadFromCloud(List<string> constructNames)
{
_downloadDataQueue = new Queue<string>();
var _gcsClient = StorageClient.Create();
foreach (var item in constructNames)
{
_downloadDataQueue.Enqueue(item);
}
while (_downloadDataQueue.Count > 0)
{
var memoryStream = new MemoryStream();
await _gcsClient.DownloadObjectAsync("companyprojects",
_downloadDataQueue.Peek(), memoryStream);
memoryStream.Position = 0;
_ = ReadFileXml(memoryStream);
_downloadDataQueue.Dequeue();
}
}
private async Task ReadFileXml(MemoryStream memoryStream)
{
var reader = new XmlReader();
var properties = reader.ReadXmlTest(memoryStream);
await Task.Run(() =>
{
var entityList = new List<Entity>();
foreach (var item in properties)
{
entityList.Add(CreateObjectsFromDownloadedProperties(item));
}
//Parallel.ForEach(properties item =>
//{
// entityList.Add(CreateObjectsFromDownloadedProperties(item));
//});
});
}
編輯
這是簡化的object創建方法:
public Entity CreateObjectsFromDownloadedProperties(RebarProperties properties)
{
var path = new LinearPath(properties.Path);
var section = new Region(properties.Region);
var sweep = section.SweepAsMesh(path, 1);
return sweep;
}
該方法的返回結果不等待,直接丟棄,立即開始下一次下載。
這也是危險的。 “一勞永逸”的意思是“我不在乎此操作何時完成,或者它是否完成。只需丟棄所有異常,因為我不在乎。” 因此,一勞永逸在實踐中應該是極其罕見的。 這里不合適。
UI 完全響應,但它不會下載下一個 object。
我不知道為什么它會阻止下載,但是切換到Parallel.ForEach
存在一個明確的問題: List<T>.Add
不是線程安全的。
private async Task ReadFileXml(MemoryStream memoryStream)
{
var reader = new XmlReader();
var properties = reader.ReadXmlTest(memoryStream);
await Task.Run(() =>
{
var entityList = new List<Entity>();
Parallel.ForEach(properties, item =>
{
var itemToAdd = CreateObjectsFromDownloadedProperties(item);
lock (entityList) { entityList.Add(itemToAdd); }
});
});
}
一個提示:如果你有一個結果值, PLINQ
通常比Parallel
更干凈:
private async Task ReadFileXml(MemoryStream memoryStream)
{
var reader = new XmlReader();
var properties = reader.ReadXmlTest(memoryStream);
await Task.Run(() =>
{
var entityList = proeprties
.AsParallel()
.Select(CreateObjectsFromDownloadedProperties)
.ToList();
});
}
然而,代碼仍然存在即發即棄的問題。
為了更好地解決問題,我建議退后一步,使用更適合“管道”式處理的東西。 例如,TPL 數據流:
public async Task DownloadFromCloud(List<string> constructNames)
{
// Set up the pipeline.
var gcsClient = StorageClient.Create();
var downloadBlock = new TransformBlock<string, MemoryStream>(async constructName =>
{
var memoryStream = new MemoryStream();
await gcsClient.DownloadObjectAsync("companyprojects", constructName, memoryStream);
memoryStream.Position = 0;
return memoryStream;
});
var processBlock = new TransformBlock<MemoryStream, List<Entity>>(memoryStream =>
{
var reader = new XmlReader();
var properties = reader.ReadXmlTest(memoryStream);
return proeprties
.AsParallel()
.Select(CreateObjectsFromDownloadedProperties)
.ToList();
});
var resultsBlock = new ActionBlock<List<Entity>>(entities => { /* TODO */ });
downloadBlock.LinkTo(processBlock, new DataflowLinkOptions { PropagateCompletion = true });
processBlock.LinkTo(resultsBlock, new DataflowLinkOptions { PropagateCompletion = true });
// Push data into the pipeline.
foreach (var constructName in constructNames)
await downloadBlock.SendAsync(constructName);
downlodBlock.Complete();
// Wait for pipeline to complete.
await resultsBlock.Completion;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.