2

I have a doubt with threading data service calls within a foreach loop. Here it goes:

Say you need to request data from a service and then process that data, for this example, let's say data request takes 2 seconds and data processing takes another one. If you had to process a list of 10000 elements how would you do it?

The thing I tried was:

Dictionary<int,Task<DataResponse>> d_tasks = Dictionary<int,Task<DataResponse>>();
for(int i=0;i<listOfElements;i++)
{
    if(i=0)
       //create and run task for first element, save it for latter use in d_tasks
    if(not last item)
       //create and run task for next element, save it for latter use in d_tasks
    if(i>0)
       //clean task data from previous element (clean d_tasks)

    d_tasks[listOfElements[i].Wait();
    DoWorkWithData(d_tasks[listOfElements[i].Result);
}

this way I was able to reduce the time in the loop, since I was able to use the latency from the service to process data.

So here is the final question, is this ok? am I forgetting something? is there some kind of pattern for this kind of situations?

Any help is appreciated.

6
  • is there some kind of pattern for this kind of situations? -- Yep. You're already using it. Commented Oct 21, 2016 at 23:51
  • Hi @RobertHarvey, thank you for the reply, so this is a pattern, cool! how is it called?
    – barclow
    Commented Oct 22, 2016 at 6:54
  • The Task objects are effectively "Futures." Collectively the technique is called "asynchronous" or "parallel" programming. Commented Oct 22, 2016 at 14:50
  • @RobertHarvey asynchronous and parallel are different kind of programming. Asynchronous can be efectively used in one thread.
    – Fabio
    Commented Apr 9, 2017 at 3:41
  • @Fabio: I never said they were the same. You can use Futures with both asynchronous and parallel programming. Please don't bother countering with a grammar observation; yes, I see it. Commented Apr 10, 2017 at 2:34

2 Answers 2

2

Not sure I understand your design. You seem to be Waiting for each and every task right after you start it. I would think you'd have two loops (one to create and start the tasks, another to go back over the results and process them). Does your single loop approach really increase parallelism?

Here's another way to do it that takes advantage of the newish Parallel.For. The nice thing here is we don't need to maintain a list of tasks or results. The lock might not be needed, depending on whether DoWorkWithData is thread-safe. I'm guessing its purpose is to compile the 10,000 results into a common data structure so it probably needs to be considered a critical section.

var lockObject = new object();
Parallel.ForEach(listOfElements,
                 (element) => { 
                                  var result = DoExpensiveParallelWork(element);
                                  lock(lockObject) { DoWorkWithData(result);}
                              });
2
  • the thing is I made a mistake on the code I posted, What I meant was: load a list of tasks, then wait for the first one to finish and process it. What I do is to mantain a list of 3 tasks asking for data to the service, not more because I don't want to overload the service.
    – barclow
    Commented Apr 10, 2017 at 15:23
  • it is a great approach though, thanks I will definetly use it in the future.
    – barclow
    Commented Apr 10, 2017 at 15:25
1

You can use async-await approach for requesting data from the service and parallel for processing data.

Create asynchronous method which will send request to the service and return result asynchronously

public async Task<DataResponse> LoadDataAsync(Element element)
{
    // await service.GetResponceAsync....
}

var loadTasks = listOfElements.Select(element => LoadDataAsync(element)).ToList();
var responses = await Task.WhenAll(loadTasks);

// Now you can process all data in parallel way
Parallel.ForEach(response => ProcessResponse(response));

The line listOfElements.Select(element => LoadDataAsync(element)).ToList() will send requests wihtout waiting for response, which makes response time for all requests to be close to response time of one request.

await Task.WhenAll(loadTasks); will wait for all responses and results will be processed in parallel.

For big amount of element you can optimize this approach by processing data straight away after response arrived. But then processing data in parallel can be difficult.

2
  • that sounds great! I mean, when using WCF with auto-generated service contract, they include Async methods in that. However, the service I'm calling does not provide any Async method, can I write an async-wrapper? how? As far as I know, Task.Run will always start a new thread.
    – barclow
    Commented Apr 10, 2017 at 15:33
  • For any other HTTP service you can use System.Net.Http.HttpClient which provide asynchronous methods.
    – Fabio
    Commented Apr 10, 2017 at 16:20

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.