我对Parallel.ForEach
有疑问。我编写了一个简单的应用程序,将要下载的文件名添加到队列中,然后使用while循环遍历队列,一次下载一个文件,然后在下载文件后,调用另一种异步方法从下载的文件中创建对象memoryStream
。不等待此方法的返回结果,它被丢弃,因此下一次下载将立即开始。如果我在对象创建过程中使用简单的foreach
,一切都会正常进行-在继续下载的同时创建对象。但是,如果我想加快对象创建过程并使用Parallel.ForEach
,它将停止下载过程,直到创建对象为止。用户界面完全响应,但不会下载下一个对象。我不明白为什么会这样-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));
//});
});
}
编辑
这是简化的对象创建方法:
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;
}
答案 0 :(得分:2)
不等待此方法的返回结果,它被丢弃,因此下一次下载将立即开始。
这也是危险的。 “解雇后忘记”的意思是“我不在乎此操作何时完成或是否完成。只需丢弃所有异常,因为我不在乎。”因此,在实践中,抛弃式的应该极为罕见。在这里不合适。
UI可以完全响应,但不会下载下一个对象。
我不知道为什么它会阻止下载,但是切换到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;
}