以有限的并发方式异步处理IEnumerable <task>

时间:2015-06-16 09:36:11

标签: c# asynchronous concurrency async-await task-parallel-library

我有一个IEnumerable<Task<T>>,其中T代表某个事件(事件的自然语言类型,而不是event类型的事件。)

我想异步处理它们,因为它们是IO绑定的,并且限制了并发量,因为处理事件的数据库不能处理多个(例如6个)并发处理请求(它们非常繁重)这样做的正确策略是什么?

如果我有

private Task processeventasync(T someevent) {
  ...
}

foreach(t in tasks) {
  await processeventsasync(await t)
}

我没有并发性。

如果我使用信号量保护事物,我实际上是在保护线程并用锁保护它们而不是异步等待它们。

https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx上的示例中的LimitedConcurrencyLevelTaskScheduler也是基于线程/锁定的方法

我已经考虑过维护最多6个任务的队列,然后围绕它做一个WhenAny循环,但感觉就像重新发明方形轮一样。

private List<Task> running = new List<Task>();

foreach(Task<T> task in tasks) {
  var inner = TaskExtensions.Unwrap(t.ContinueWith(tt => processeventasync(tt.Result)));
  running.Add(inner);
  if (running.Count >= 6) {
    var resulttask = await Task.WhenAny(running); 
    running.Remove(resulttask);
    await resulttask;
    //not sure if this await will schedule the next iteration
    //of the loop asynchronously, or if the loop happily continues
    //and the continuation has the rest of the loop body (nothing
  }
}

什么是正确的方式去这里?

编辑:

SemaphoreSlim s WaitAsync似乎非常合理。我将看到以下奇怪的代码:

    private async void Foo()
    {

        IEnumerable<Task<int>> tasks = gettasks();
        var resulttasks = tasks.Select(ti => TaskExtensions.Unwrap(ti.ContinueWith(tt => processeventasync(tt.Result))));
        var semaphore = new SemaphoreSlim(initialCount: 6);

        foreach (Task task in resulttasks)
        {
            await semaphore.WaitAsync();
            semaphore.Release();
        }
    }

这里有async void相当臭,但这是一个无限循环;它永远不会返回(实际处理显然会有一些取消机制)。

看起来身体中的等待/释放看起来很奇怪,但它看起来确实是正确的。这是一种没有隐藏陷阱的合理方法吗?

2 个答案:

答案 0 :(得分:3)

您可以使用SemaphoreSlim.WaitAsync限制并发。

  

只是身体中的等待/释放看起来很奇怪,但是   它看起来确实是正确的

您当前的方法并没有真正做任何事情。这些任务完全不受SemaphoreSlim的影响,因为您使用Enumerable.Select同时调用它们。

您需要监控Select内的信号量:

private const int ConcurrencyLimit = 6;
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(ConcurrencyLimit);

public async Task FooAsync()
{
    var tasks = GetTasks();
    var sentTasks = tasks.Select(async task =>
    {
       await semaphoreSlim.WaitAsync();
       try
       {
          await ProcessEventAsync(await task);
       }
       finally
       {
           semaphoreSlim.Release();
       }
    });

    await Task.WhenAll(sentTasks);
}

private Task ProcessEventAsync(T someEvent) 
{
    // Process event.
}

答案 1 :(得分:1)

您可以使用TPL Dataflow&#39; ActionBlock<T>

定义处理事件的操作块,然后将要处理的项目发布到此块。您还可以设置最大并行度。

var block = new ActionBlock<string>(str =>
{
    //save in db
}, new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = 6
});

var sendings = new List<Task<bool>>
{
    block.SendAsync("a"),
    block.SendAsync("b"),
    block.SendAsync("c")
};

await Task.WhenAll(sendings);

block.Complete();       // tell the block we're done sending messages
await block.Completion; // wait for messages to be processed