我需要创建一个处理大量图像的程序。该过程中大约有10个不同阶段需要按顺序发生。
我想问一下,创建一个管道是否更好,其中每个处理阶段都有自己的线程,并使用此处描述的管道模式在其间进行缓冲: https://msdn.microsoft.com/en-us/library/ff963548.aspx
或创建一个线程池,并使用Parallel.Foreach将一个图像分配给一个线程。
为什么?
答案 0 :(得分:0)
也许这对你来说是新的东西,但是你可以使用actor系统getakka.net,其中所有作业都由actor完成,因此你可以为每个阶段创建actor并将图像从一个actor传递给另一个actor。 我使用这个框架,因为非阻塞方式的并行处理有了很大的改进。它也可以轻松扩展。
答案 1 :(得分:0)
老实说,没有实际基准测试就没有办法告诉它。但是,您实际上可以使用TPL Dataflow同时并行和管道。
管道中的每个阶段都是TransformBlock<TInput, TOutput>
,然后可以并行处理的阶段可以设置Degree of Parallelism。
这是一个示例(用浏览器编写,因此可能有错误),它加载带有3级管道的图像,用于从磁盘读取,裁剪图像,然后将其写回磁盘。读写阶段一次只能处理1个图像,但裁剪阶段将同时处理5个图像。此外,管道只允许100个图像排队写入,还有100个图像排队等待裁剪。如果管道已满,它将停止读取图像,并等待管道中有空间(防止过度使用RAM)。
public async Task CropImages(string directory, int x, int y)
{
var loadImage = new TransformBlock<String, MyImage>(LoadImageAsync);
var cropImage = new TransformBlock<MyImage, MyImage>((image) => Crop(image, x, y),
new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 5});
var saveImage = new ActionBlock(SaveImageAsync);
loadImage.LinkTo(cropImage, new DataflowLinkOptions {PropagateCompletion = true, MaxMessages = 100});
cropImage.LinkTo(saveImage, new DataflowLinkOptions {PropagateCompletion = true, MaxMessages = 100});
foreach(var file in Directory.EnumerateFiles(directory, "*.jpg"))
{
await loadImage.SendAsync(file);
}
loadImage.Complete();
await saveImage.Completion;
}
private async Task<MyImage> LoadImageAsync(string fileName)
{
byte[] data = await GetDataAsync(fileName);
return new MyImage(data, fileName);
}
private MyImage Crop(MyImage image, int x, int y)
{
image.Crop(x,y);
return image;
}
private async Task SaveImageAsync(MyImage image)
{
var fileName = Path.GetFileName(image.FileName);
var directoryName = Path.GetDirectoryName(image.FileName);
var newName = Path.Combine(directoryName, "Cropped-" + filename);
await SaveDataAsync(image.Bytes, newName);
}