Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for a specific amount of time after creating a specific number of tasks\Threads?

I have a requirement where I can hit an API 5 times in a second. If I have to make a total of 50 requests, I want to make the first 5 requests and wait for 1 second before I can hit the API with another batch of 5 requests. I tried using Thread pool as well as Parallel task library For\Foreach loops and Task classes but I am unable to get a sequential counter that would tell me that 5 Tasks have been created. Here is a sample of what I am trying to do:

List<string> str = new List<string>();
for (int i = 0; i <= 100; i++)
{
    str.Add(i.ToString());
}

Parallel.ForEach(str, new ParallelOptions { MaxDegreeOfParallelism = 5 },
(value, pls, index) =>
{
    Console.WriteLine(value);// simulating method call
    if (index + 1 == 5)
    {
        // need the main thread to sleep so next batch is 
        Thread.Sleep(1000);
    }
});
like image 211
Ridhima Muglani Avatar asked Feb 04 '26 19:02

Ridhima Muglani


1 Answers

Since you're using .NET 4.0 (and assuming, hopefully, that you're at least using VS2012), you can use Microsoft.Bcl.Async to get async-await features.

Once you do that, you can easily query your API endpoint asynchronously (no need for extra threads for that), and use a AsyncSemaphore (see implementation below) to cap the number of requests you do concurrently.

For example:

public readonly AsyncSemaphore = new AsyncSemaphore(5);
public readonly HttpClient httpClient = new HttpClient();
public async Task<string> LimitedQueryAsync(string url)
{
    await semaphoreSlim.WaitAsync();
    try
    {
        var response = await httpClient.GetAsync(url);
        return response.Content.ReadAsStringAsync();
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

Now you can query it like this:

public async Task DoQueryStuffAsync()
{
    while (someCondition)
    {
        var results = await LimitedQueryAsync(url);

        // do stuff with results
        await Task.Delay(1000);
    }
}

Edit: As @ScottChamberlain points out correctly, SemaphoreSlim isn't avaliable in .NET 4. You can instead use AsyncSemaphore, which looks as follows:

public class AsyncSemaphore 
{ 
    private readonly static Task s_completed = Task.FromResult(true); 
    private readonly Queue<TaskCompletionSource<bool>> m_waiters = 
                                            new Queue<TaskCompletionSource<bool>>(); 
    private int m_currentCount; 

    public AsyncSemaphore(int initialCount)
    {
        if (initialCount < 0) 
        {
            throw new ArgumentOutOfRangeException("initialCount"); 
        }
        m_currentCount = initialCount; 
    }

    public Task WaitAsync() 
    { 
        lock (m_waiters) 
        { 
            if (m_currentCount > 0) 
            { 
                --m_currentCount; 
                return s_completed; 
            } 
            else 
            { 
                var waiter = new TaskCompletionSource<bool>(); 
                m_waiters.Enqueue(waiter); 
                return waiter.Task; 
            } 
        } 
    }

    public void Release() 
    { 
        TaskCompletionSource<bool> toRelease = null; 
        lock (m_waiters) 
        { 
            if (m_waiters.Count > 0) 
                toRelease = m_waiters.Dequeue(); 
            else 
                ++m_currentCount; 
        } 
        if (toRelease != null) 
            toRelease.SetResult(true); 
    }
}
like image 60
Yuval Itzchakov Avatar answered Feb 06 '26 08:02

Yuval Itzchakov