0

I would like to implement a pool of a predetermined number (let's say 10) asynchronous tasks running undefinitely.

Using Task.WhenAll, I can easily start 10 tasks, feed them into a list, and call await Task.WhenAll(list) on this list. Once the method comes back, I can start again the whole process on the next 10 elements. The problem I face with this solution is that it waits for the longest task to complete before looping, which is not optimal.

What I would like is that anytime a task is completed, a new one is started. A timeout would be great as well, to prevent a task from being run undefinitely in case of a failure.

Is there any simple way of doing this?

Jay Buckman
  • 581
  • 5
  • 18
XavierAM
  • 1,627
  • 1
  • 14
  • 30
  • 2
    Have you considered `WhenAny` instead of `WhenAll`? – ColinM Feb 24 '20 at 12:41
  • 1
    Please, share your code – Pavel Anikhouski Feb 24 '20 at 12:41
  • 2
    Does this answer your question? [Have a set of Tasks with only X running at a time](https://stackoverflow.com/questions/14075029/have-a-set-of-tasks-with-only-x-running-at-a-time) – devNull Feb 24 '20 at 12:54
  • 1
    Is this a job for [`TPL DataFlow`](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library?redirectedfrom=MSDN) perhaps? – Matthew Watson Feb 24 '20 at 13:07
  • @ColinM yes you are right, but can I add an element to the list being awaited? – XavierAM Feb 24 '20 at 13:32
  • @devNull This is the exact same question, but answers are quite old, I was wondering if there was any updated way of doing it. Most of them do not use the async/await syntax.Thanks for the link anyway – XavierAM Feb 24 '20 at 13:36
  • @MatthewWatson I didn't know this library, it looks really promising! – XavierAM Feb 24 '20 at 13:40
  • 1
    The answers are quite old, but the only thing that's really changed with TPL since that time is the addition of `async`/`await`. – ColinM Feb 24 '20 at 13:41
  • 1
    @XavierAM It has quite a steep learning curve, but it's the best library for "processing pipeline" style problems, IMO. – Matthew Watson Feb 24 '20 at 13:41
  • @ColinM point taken, actually one of the answer matches the TPL recommandation from Matthew. – XavierAM Feb 24 '20 at 13:45
  • For throttling I/O operations you can look here: [How to limit the amount of concurrent async I/O operations](https://stackoverflow.com/questions/10806951/how-to-limit-the-amount-of-concurrent-async-i-o-operations). – Theodor Zoulias Feb 24 '20 at 14:37

1 Answers1

3

What I would like is that anytime a task is completed, a new one is started.

This is a perfect use case for SemaphoreSlim:

private readonly SemaphoreSlim _mutex = new SemaphoreSlim(10);

public async Task AddTask(Func<Task> work)
{
  await _mutex.WaitAsync();
  try { await work(); }
  finally { _mutex.Release(); }
}

A timeout would be great as well, to prevent a task from being run undefinitely in case of a failure.

The standard pattern for timeouts is to use a CancellationTokenSource as the timer and pass a CancellationToken into the work that needs to support cancellation:

private readonly SemaphoreSlim _mutex = new SemaphoreSlim(10);

public async Task AddTask(Func<CancellationToken, Task> work)
{
  await _mutex.WaitAsync();
  using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
  try { await work(cts.Token); }
  finally { _mutex.Release(); }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810