Reputation: 20372
Can someone please show how to make concurrent requests without creating multiple threads? E.g., I want a program that makes 100 web requests and I don't want more than 8 concurrent requests at any time. I don't want to create 8 threads for the 8 concurrent requests. When a thread makes an async request, the same thread can then be used to make the next request, and so on. I am sorry but I can't wrap my head around this, and would like to see the best solution out there. In case it wasn't clear, the requests I am talking about are async. I want to see a solution that does not use any locks, and uses the built-in classes to do the work.
This is some code I came up with but it does not do what it is supposed to do.
Task.Run(async () =>
{
var outstandingRequests = 0;
var requestCount = 0;
var tasks = new List<Task>(concurrentRequests);
while (requestCount < maxRequests)
{
if (outstandingRequests < concurrentRequests)
{
tasks.Add(svc.GetDataAsync()); // a method that makes an async request
Interlocked.Increment(ref outstandingRequests);
}
else
{
var t = await Task.WhenAny(tasks);
Interlocked.Decrement(ref outstandingRequests);
Interlocked.Increment(ref requestCount);
}
}
await Task.WhenAll(tasks);
}).Wait();
Output:
[] 1 Sending Request...Received Response 490,835.00 bytes in 15.6 sec
[] 2 Sending Request...
[] 3 Sending Request...
[] 4 Sending Request...
[] 5 Sending Request...
[] 6 Sending Request...
[] 7 Sending Request...
[] 8 Sending Request...
[] 9 Sending Request...
I have set concurrentRequests
to 5, so there is some bug in above code as it is making 8 requests in parallel. Initially it made only 5 requests in parallel, but as soon as one request completed, it fired off 4 more requests (should have fired off only one more).
Had to fix some bugs, but it all works out now:
Task.Run(async () =>
{
var outstandingRequests = 0;
var requestCount = 0;
// adding and removing from a List<> at the same time is not thread-safe,
// so have to use a SynchronizedCollection<>
var tasks = new SynchronizedCollection<Task>();
while (requestCount < maxRequests)
{
if (outstandingRequests < concurrentRequests)
{
tasks.Add(svc.GetDataAsync(uri)); // this will be your method that makes async web call and returns a Task to signal completion of async call
Interlocked.Increment(ref outstandingRequests);
Interlocked.Increment(ref requestCount);
}
else
{
**tasks.Remove(await Task.WhenAny(tasks));**
Interlocked.Decrement(ref outstandingRequests);
}
}
await Task.WhenAll(tasks);
}).Wait();
If there is a better way to do it, please let me know.
Upvotes: 4
Views: 2334
Reputation: 20086
How about this:
Parallel.Invoke (new ParallelOptions { MaxDegreeOfParallelism = 8 },
svcs.Select (svc => svc.GetDataAsync ()).ToArray ()) ;
There is a sample Microsoft implementation of a limited-concurrency task scheduler here. See SO questions System.Threading.Tasks - Limit the number of concurrent Tasks and .Net TPL: Limited Concurrency Level Task scheduler with task priority?.
Upvotes: 1
Reputation: 2444
Looks like you are trying to reinvent the thread pool. Don't do that - just use existing functionality: http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx
Or you can use async versions of request methods - they are based on the thread pool too.
Upvotes: 4