6
\$\begingroup\$

I've got some methods that follow a similar pattern, and wanted to abstract some of the logic away into an extension method. Specifically, I am processing lists of objects with the TPL, and throttling that processing with a SemaphoreSlim.

public static Task ValidateProxiesAsync(IList<Proxy> proxies, IList<ProxyJudge> judges, IList<ProxyTest> tests)
{
    return Task.Run(async () =>
    {
        await ValidateTestsAsync(judges.ToList<IProxyTest>());
        await ValidateTestsAsync(tests.ToList<IProxyTest>());
        var semaphore = new SemaphoreSlim(100);
        var tasks = new List<Task>();
        foreach (var proxy in proxies)
        {
            await semaphore.WaitAsync();
            tasks.Add(Task.Run(() =>
            {
                proxy.Status = proxy.TestValidity(judges[0]);
                semaphore.Release();
            }));
        }
        await Task.WhenAll(tasks);
    });
}

public static Task ValidateTestsAsync(IList<IProxyTest> tests)
{
    return Task.Run(async () =>
    {
        var semaphore = new SemaphoreSlim(100);
        var tasks = new List<Task>();
        foreach (var test in tests)
        {
            await semaphore.WaitAsync();
            tasks.Add(Task.Run(() =>
            {
                test.Status = test.TestValidity();
                semaphore.Release();
            }));
        }
        await Task.WhenAll(tasks);
    });
}

I wrote this extension method to reduce the LOC needed to do this. But I wanted to make sure I didn't overlook something. Perhaps there is a better solution that someone on here could suggest (TPL Dataflow?).

Extension method:

    public static Task ProcessWithSemaphoreAsync<T>(this IList<T> list, Action<T> action, int maxConcurrency)
    {
        return Task.Run(async () => {
            var semaphore = new SemaphoreSlim(maxConcurrency);
            var tasks = new List<Task>();
            foreach (var item in list)
            {
                await semaphore.WaitAsync();
                tasks.Add(Task.Run(() =>
                {
                    action.Invoke(item);
                    semaphore.Release();
                }));
            }
            await Task.WhenAll(tasks);
        });
    }
\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

The only thing I can see is that you should dispose the semaphore:

using (var semaphore = new SemaphoreSlim(maxConcurrency))
{
  var tasks ....
}

And you could generalize to IEnumerable<T> instead of IList<T>


A simple solution using Parallel.ForEach() could be:

public static void ProcessWithParallelAsync<T>(this IEnumerable<T> list, Action<T> action, int maxConcurrency)
{
  ParallelOptions options = new ParallelOptions();
  options.MaxDegreeOfParallelism = maxConcurrency;
  Parallel.ForEach(list, options, action);
}

Or you could wrap it in a Task if you like:

public static Task ProcessWithParallelAsync<T>(this IEnumerable<T> list, Action<T> action, int maxConcurrency)
{
  return Task.Run(() =>
  {
    ParallelOptions options = new ParallelOptions();
    options.MaxDegreeOfParallelism = maxConcurrency;

    Parallel.ForEach(list, options, action);
  });
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ This does not work when the action itself must be asynchronous. \$\endgroup\$
    – Dinerdo
    Commented Aug 8, 2019 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.