繁体   English   中英

如何使用多个 ActionBlock 并行化列表?

[英]How to parallelize a list using multiple ActionBlocks?

我有一个大型文件夹结构,我正试图从共享驱动器下载。 共享驱动器很慢,但它也有几个镜像。 为了加快这个过程,我正在尝试制作一个小的下载器应用程序来管理与所有慢速镜像的并行连接。 单个文件将从不同的镜像下载。 我还希望能够限制一次连接到每个镜像的线程数。 (这已经存在了吗?那我就不用写任何代码了。不过我确实看过。)

这似乎可能是一个 Dataflow 用例,尽管我对 Dataflow 很陌生,所以我并不积极。 我从这样的事情开始:

var buffer = new BufferBlock<string>();
var blockOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = threadsPerPath
};

IEnumerable<ActionBlock<string>> blocks = mirrors.Select(basePath =>
{
    return new ActionBlock<string>(
        file => {
            string destinationFile = Path.Combine(destination, file);
            Directory.CreateDirectory(Path.GetDirectoryName(destinationFile));
            File.Copy(Path.Combine(basePath, file), destinationFile);
        },
        blockOptions);
});

foreach (ActionBlock<string> block in blocks)
{
    buffer.LinkTo(block);
}

await Task.Run(() =>
{
    string top = mirrors[0];

    int baseLength = top.Length;

    IEnumerable<string> allFiles = Directory.EnumerateFiles(top, "*", SearchOption.AllDirectories);

    foreach (string path in allFiles)
    {
        buffer.Post(path[baseLength..]);
    }

    buffer.Complete();
});

(我打算玩threadsPerPath 。不确定我是否会看到并行访问同一个镜像的好处。)运行时,它只使用第一个镜像 - 据我所知,其他镜像的 ActionBlocks 永远不会获取数据. 我认为这是设计使然,但我不知道该怎么做。 如何让多个 ActionBlocks 并行处理同一个缓冲区,其中缓冲区中的每个项目只进入其中一个 ActionBlocks?

限制跨多个ActionBlock<T>的并行化的一种方法是使用同一ConcurrentExclusiveSchedulerPair实例的ConcurrentScheduler配置所有这些:

var sharedScheduler = new ConcurrentExclusiveSchedulerPair(
    TaskScheduler.Default, maxConcurrencyLevel: 50).ConcurrentScheduler;

var blockOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = threadsPerPath,
    TaskScheduler = sharedScheduler
};

这仅在ActionBlock<T>的操作是同步的情况下才有效,因为它在您发布的代码中。 您不能使用TaskScheduler选项限制异步工作的并发性。 在这种情况下,您必须在action中使用共享的SemaphoreSlim

要实现负载平衡,您需要限制链接块的输入容量。 如果要将请求传播到所有块,则必须限制它们的 BoundedCapacity,甚至可能限制为 1 或至少与MaxDegreeOfParallelism相同的数字

var blockOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = threadsPerPath,
    BoundedCapacity=bound
};

原始代码的问题是所有ActionBlock都有无限的输入缓冲区,因此所有消息(文件)都在第一个块中结束。

块的 output 被发送到第一个可用的链接块。 如果该块具有无限缓冲区,则所有消息都将发送到同一个块。 通过将所有块的输入缓冲区限制为仅一项,您可以强制将每条消息(在本例中为文件)发送到不同的块。

发布消息后,您可以等待所有块完成:

await Task.WhenAll(blocks.Select(b=>b.Completion));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM