如何实现ForEachAsync?

时间:2019-06-10 08:01:38

标签: c# multithreading

处理多线程执行

1 个答案:

答案 0 :(得分:4)

似乎实际的问题应该是:

  

如何在不按顺序执行的情况下更改AWS Athena中许多客户端的数据库分区?

答案不是ForEachAsync还是C#8中即将到来的await foreach。异步循环仍会一次向服务发送一次调用,它“只是”在等待时不会阻塞寻求答案。

在职员工

这是一个并发的工作程序问题,可以使用TPL Dataflow库的ActionBlock类或新的System.Threading.Channel类来处理。

Dataflow库旨在通过在独立块之间移动数据来创建类似于Shell脚本管道的事件/消息处理管道。每个块都在自己的任务/线程上运行,这意味着您只需将处理分解为多个块即可获得并发执行。

在创建块时,还可以增加每个块的处理任务数by specifying the MaxDegreeOfParallelism option。这使我们可以快速创建可以同时处理大量消息的“工人”。

示例

在这种情况下,“消息”即为Client。一个ActionBlock可以创建DDL语句 并执行它。每个块都有一个输入队列,这意味着我们可以将消息发布到一个块,然后等待它使用指定的DOP执行所有操作。

我们还可以为队列指定一个限制,这样,如果工作者任务不能足够快地运行,就不会淹没它:

var options=new ExecutionDataflowBlockOptions
     {
        MaxDegreeOfParallelism = _maxclientsToBeProcessed,
        BoundedCapacity = _maxclientsToBeProcessed*3, //Just a guess
     });
var block=new ActionBlock<Client>(client=>CreateAndRunDDL(client));

//Post the client requests
foreach(var client in clients)
{
    await block.SendAsync(client);
}

//Tell the block we're done
block.Complete();
//Await for all queued messages to finish processing
await block.Completion;

CreateAndRunDDL(Client)方法应该执行问题循环内的代码。一个好主意是重构它,并创建单独的函数来创建和执行查询,例如:

async Task CreateAndRunDDL(Client client)
{
    var query = QueryForClient(...);
    LambdaLogger.Log(query);
    if (query.Length >= MaxQueryLength) {
        throw new Exception("Delete partition query length exceeded.");
    }
    var queryExecutionId = await StartQueryExecution(query);
    await CheckQueryExecutionStatus(queryExecutionId);
}

块也可以链接。如果我们想将多个客户分批处理,我们可以使用BatchBlock并将其结果输入到我们的操作块中,例如:

var batchClients = new BatchBlock<Client>(20);
var linkOptions = new DataflowLinkOptions
                  { 
                      PropagateCompletion = true
                  };

var block=new ActionBlock<Client>(clients=>CreateAndRunDDL(clients));
batchClients.LinkTo(block,linkOptions);

这次,CreateAndRunDDL方法接受一个Client[]数组,该数组具有我们在批处理大小中指定的客户/消息数。

async Task CreateAndRunDDL(Client[] clients)
{
    var query = QueryForClients(clients);
    ...
}

现在应该将消息发布到batchClients块中。完成后,我们需要等待管道中的最后一个块完成:

foreach(var client in clients)
{
    await batchClients.SendAsync(client);
}

//Tell the *batch block* we're done
batchClient.Complete();
//Await for all queued messages to finish processing
await block.Completion;