我试图获取用户在其个人资料中发布的所有图片。我使用API执行此操作,api显示最多50个用户发布的最新帖子。要获得超过50个,你需要拨打第二页,每个页面有50个,所以如果你想要最后200个页面,请运行4页。
我开始使用并行技术,否则它将需要很长时间。我问的问题是,有没有办法完全停止并行的每一次交互,一旦它到达一个没有更多图像需要收集的阶段?
从下面可以看到,我有一个待办事项。现在我可以突破它,但实际上有些互动比他们需要的更进一步。有没有办法在任何并行部分达到无法收集图像的限制时立即停止所有交互?
public List<TumblrFile> GetImagesForAddress(string pageAddress, string saveLocation)
{
var imageUrls = new List<TumblrFile>();
Parallel.For(0, _pageCount,
index => {
var document = XDocument.Load(GetApiLink(pageAddress, index * 50, true));
var newImages = XmlUtilities.ExtractImagesFromDocument(document, saveLocation);
if (newImages.Count < 1)
{
// TODO: Stop this loop
}
foreach (var newImage in newImages)
{
if (imageUrls.All(x => x.Address != newImage.Address))
{
imageUrls.Add(newImage);
}
}
});
return imageUrls;
}
答案 0 :(得分:2)
有一个并行的重载,看起来像这个
Parallel.For(0, pageCount, (index, loopState) =>
使用它,当你想打破说 loopState.Stop();
答案 1 :(得分:1)
假设您的_pagecount为4,但资源只有60张图片。您的代码将创建4个任务并由TaskScheduler相应地执行。你可以假设它是平行的。
你期望什么是任务3什么都不做,然后任务4应该中止,而任务1和2应该继续,直到结束。
如果我的理解有效,Parallel.For(0, _pageCount, (index, loopstate) =>
将无法提供帮助,因为它会停止个别任务,而不关心其余任务。
为了实现您的期望,可以有两个选项:
选项1:假设您可以知道总图像的数量。 然后,您需要做的就是动态计算_pageCount。
选项2:假设您不知道总图像的数量。 你可以像这样有一个while循环:
while (true)
{
var result = ParallelGetImage(yourLevelOfParallelism);
if (result == null)
{
break;
}
}
答案 2 :(得分:0)
我建议不要使用Parallel.For
。微软拥有一个名为“Reactive Framework”(或Rx.NET)的优秀库,可以很好地完成这种工作。
以下是GetImagesForAddress
方法所需的内容。
// Effectively infinite pages of images query
IObservable<List<TumblrFile>> observableOfListsOfImages=
from index in Observable.Range(0, int.MaxValue)
from document in Observable.Start(() => XDocument.Load(GetApiLink(pageAddress, index * 50, true)))
from images in Observable.Start(() => XmlUtilities.ExtractImagesFromDocument(document, saveLocation))
select images;
// Stop query when empty list returned
// and wait for results
TumblrFile[] arrayOfImages =
observableOfListsOfImages
.TakeWhile(x => x.Any())
.SelectMany(x => x)
.ToArray()
.Wait();
// Remove any duplicates based on `.Address` property
List<TumblrFile> listOfImages =
arrayOfImages
.GroupBy(x => x.Address)
.SelectMany(x => x.Take(1))
.ToList();
return listOfImages;
这是你方法的全部内容。
它完成所有线程并为您处理所有并发。
您只需要将“System.Reactive”添加为NuGet包,然后将System.Reactive.Linq
命名空间添加到您的代码中。
也无需限制_pageCount
(但您可以换掉int.MaxValue
)。
最后,还有更好的可能性。您可以返回IObservable<TumblrFile>
而不是List<TumblrFile>
,以便能够在生成文件后立即获取这些文件。你的代码看起来像这样:
public IObservable<TumblrFile> GetImagesForAddress2(string pageAddress, string saveLocation)
{
// Effectively infinite pages of images query
IObservable<List<TumblrFile>> observableOfListsOfImages=
from index in Observable.Range(0, int.MaxValue)
from document in Observable.Start(() => XDocument.Load(GetApiLink(pageAddress, index * 50, true)))
from images in Observable.Start(() => XmlUtilities.ExtractImagesFromDocument(document, saveLocation))
select images;
// Stop query when empty list returned
// and remove duplicates by `.Address`
return
observableOfListsOfImages
.TakeWhile(x => x.Any())
.SelectMany(x => x)
.GroupBy(x => x.Address)
.SelectMany(x => x.Take(1));
}
您只需致电.Subscribe(image => /* code here to process each image */);
即可处理结果。
答案 3 :(得分:-1)
执行此操作的一种干净方法是在找到限制后更新共享变量,然后在每次调用开始时检查是否已找到限制以及索引是否超出限制,不执行任何操作
您需要这样做,因为您不能保证并行执行的顺序,并且您不希望停止可能尚未启动的较低索引。
要注意的一件事是,如果在以后的执行中发现下限,则可能需要多次更新共享变量。您可以变得聪明并使用Interlocked.CompareAndExchange
来执行此操作,或者,从简单开始并使用lock
语句围绕对此共享“结束值”的所有访问。
或者,您可以像其他人建议的那样使用循环状态(现在已删除),但您需要调用Break()
而不是Stop()
,以便具有较早索引的其他线程继续运行,您需要检查每次调用以获取网页之前的loopState.IsStopped
。见https://msdn.microsoft.com/en-us/library/dd460721(v=vs.110).aspx
在任何一种情况下,您都应该在Parallel.For
上设置最大并行度,以限制它同时进行多少次api调用。