Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly queue up tasks to run in C#

Tags:

I have an enumeration of items (RunData.Demand), each representing some work involving calling an API over HTTP. It works great if I just foreach through it all and call the API during each iteration. However, each iteration takes a second or two so I'd like to run 2-3 threads and divide up the work between them. Here's what I'm doing:

ThreadPool.SetMaxThreads(2, 5); // Trying to limit the amount of threads var tasks = RunData.Demand    .Select(service => Task.Run(async delegate    {       var availabilityResponse = await client.QueryAvailability(service);       // Do some other stuff, not really important    }));  await Task.WhenAll(tasks); 

The client.QueryAvailability call basically calls an API using the HttpClient class:

public async Task<QueryAvailabilityResponse> QueryAvailability(QueryAvailabilityMultidayRequest request) {    var response = await client.PostAsJsonAsync("api/queryavailabilitymultiday", request);     if (response.IsSuccessStatusCode)    {       return await response.Content.ReadAsAsync<QueryAvailabilityResponse>();    }     throw new HttpException((int) response.StatusCode, response.ReasonPhrase); } 

This works great for a while, but eventually things start timing out. If I set the HttpClient Timeout to an hour, then I start getting weird internal server errors.

What I started doing was setting a Stopwatch within the QueryAvailability method to see what was going on.

What's happening is all 1200 items in RunData.Demand are being created at once and all 1200 await client.PostAsJsonAsync methods are being called. It appears it then uses the 2 threads to slowly check back on the tasks, so towards the end I have tasks that have been waiting for 9 or 10 minutes.

Here's the behavior I would like:

I'd like to create the 1,200 tasks, then run them 3-4 at a time as threads become available. I do not want to queue up 1,200 HTTP calls immediately.

Is there a good way to go about doing this?

like image 262
Mike Christensen Avatar asked Nov 25 '15 23:11

Mike Christensen


People also ask

What is Task run ()?

The Run method allows you to create and execute a task in a single method call and is a simpler alternative to the StartNew method. It creates a task with the following default values: Its cancellation token is CancellationToken.

Does Task run need to be awaited?

If it is some trivial operation that executes quickly, then you can just call it synchronously, without the need for await . But if it is a long-running operation, you may need to find a way to make it asynchronous.

Does Task Run Run async?

NET, Task. Run is used to asynchronously execute CPU-bound code. Let's say there is a method which does some CPU-bound work. Example : looping through a large array and doing some complex computation on each element of the array.


1 Answers

As I always recommend.. what you need is TPL Dataflow (to install: Install-Package System.Threading.Tasks.Dataflow).

You create an ActionBlock with an action to perform on each item. Set MaxDegreeOfParallelism for throttling. Start posting into it and await its completion:

var block = new ActionBlock<QueryAvailabilityMultidayRequest>(async service =>  {     var availabilityResponse = await client.QueryAvailability(service);     // ... }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });  foreach (var service in RunData.Demand) {     block.Post(service); }  block.Complete(); await block.Completion; 
like image 73
i3arnon Avatar answered Sep 17 '22 10:09

i3arnon