簡體   English   中英

處理IEnumerable <Task> 異步,並發性有限

[英]Process IEnumerable<Task> asynchronously, with limited concurrency

我有一個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
  }
}

什么是正確的方式去這里?

編輯:

SemaphoreSlimWaitAsync似乎非常合理。 我要看到以下奇怪的代碼:

    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在這里很臭,但它是一個無限循環; 它永遠不會返回(實際處理顯然會有一些取消機制)。

只是身體中的等待/釋放看起來很奇怪,但它看起來確實是對的。 這是一種沒有隱藏陷阱的合理方法嗎?

您可以使用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.
}

您可以使用TPL DataflowActionBlock<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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM