如何动态限制Parallel.Foreach创建的任务的并发执行

时间:2017-05-02 08:48:40

标签: .net task-parallel-library parallel.foreach taskscheduler

我正在创建一个针对一组资源执行命令的应用程序。例如,资源可以是服务器。任务可能是" ping"或者"整理数据库索引"例如(这些是示例,因为我无法揭示应用程序的真实性质)。 应用程序使用TPL(Parallel.ForEach)同时执行此命令。

现在我已经到了必须限制针对存储在公共资源上的资源的命令的并发执行的程度。例如,这里有一个必须进行碎片整理的DB索引列表。

Task# | Server | DB  | Index Name
---------------------------------
T1    | 1      | DB1 | A
T2    | 1      | DB1 | B
T3    | 1      | DB1 | C
T4    | 1      | DB2 | D
T5    | 2      | DB3 | E
T6    | 2      | DB3 | F
T7    | 3      | DB4 | G
T8    | 4      | DB5 | H
T9    | 6      | DB6 | I

为了防止服务器上的大量并行磁盘I / O,我必须将索引碎片整理任务的执行限制为每个服务器一个。 这意味着任务T7,T8& T9可以同时运行。 T1-T4中只有一个任务必须同时运行。对于T5和T5的任务也是如此。 T6。 例如,还应该可以将并发执行限制为每个资源2个任务。我怎样才能实现这一目标?

我已经查看了TaskScheduler类,但似乎TaskScheduler无法拒绝任务(QueueTask方法)。另外一个自定义分区程序也不会让我到那里。

我能想到的唯一解决方案是创建我自己的Parallel.ForEach实现,它可以完全处理这个限制。

有没有人有更好的主意?

如上所述,这是一个示例用例。因此,请不要发布以下答案:为此创建SQL代理作业。

1 个答案:

答案 0 :(得分:1)

这个例子不是最合适的。尝试同时对多个索引进行碎片整理将导致更差的性能,因为碎片整理操作将争用相同的CPU和磁盘资源。并发碎片整理操作运行得更快的唯一方法是将每个索引存储在不同的磁盘(阵列)中。

您也可以在一个脚本中编写所有defrag语句并运行它。

在一般情况下,您希望为具有有限并行性的并发执行排队某些作业/操作,相应的库为TPL DataflowActionBlock class。 ActionBlock允许您将消息发布到块以进行并发执行。默认情况下,一次只处理一条消息。您可以在创建块时简单地传递不同的DOP参数来处理更多消息。

var dopOptions=new ExecutionDataflowBlockOptions
               {
                   MaxDegreeOfParallelism = maxDop
               }
var defragBlock=new ActionBlock<string)(indexName=>MyDefragMethod(indexName), dopOptions);

//Post all the indexes. Only maxDop will be processed at a time
foreach(var indexName in indexesList)
{
    defragBlock.Post(indexName);
}

//Notify the block that we are done
defragBlock.Complete();

//and wait for all remaining indexes to finish
await defragBlock.Completion;

Parallel方法不适合作业处理方案。它们用于数据并行方案。它们旨在通过对数据进行分区并使用每个分区一个任务对其进行处理来处理大量数据。分区数与核心数大致相同。

Dataflow库允许您使用更高级的方案。例如,如果您定位多个服务器,该怎么办?在这种情况下,您可以同时在每个服务器上运行碎片整理操作。您可以通过为每个服务器创建一个不同的块来传递不同的连接字符串,例如

var block1=new ActionBlock<string)(indexName=>MyDefragMethod(indexName,connStr1), dopOptions);
var block2 =new ActionBlock<string)(indexName=>MyDefragMethod(indexName,connStr2), dopOptions);

foreach(var indexName in indexesList1)
{
    block1.Post(indexName);
}
foreach(var indexName in indexesList2)
{
    block2.Post(indexName);
}

//Notify the block that we are done
block1.Complete();
block2.Complete();

//and wait for all remaining indexes to finish
await Task.WhenAll(block1.Completion,block2.Completion);

您还可以将这些块存储在由服务器名称键入的字典中,以便您可以在循环内选择正确的块。