Search code examples
c#asynchronousparallel.foreach

Is my code really executed asynchronously?


I have several objects in the list. Every minute, for each of them, I have to download api information and save received data to the database.

Objects in the list are independent of each other. The method for each object can be called separately. Depending on the response time from api, the total execution time for a single method is usually from ~0.5 up to 5 seconds.

Following the suggestions in other threads on stackoverflow, I created the following code:

private async void DownloadAndSaveDataForMyObjects(object state)
{
   try
   {
      await Task.Run(() => Parallel.ForEach(MyObjectsList, ServiceSingleObjectMethod));
   }
}


private void ServiceSingleObjectMethod(RehDeviceSmsRequest myObjectFromList)
{
   var apiInfo = GetInfoFromApi(myObjectFromList);
   SaveInfoToDatabase(myObjectFromList);
}

In my opinion the code is asynchronous. But... I have some doubts. Logger inform me about the actions carried out during application running.The intervals between method calls for objects are quite large. Please look this:

[13:32:46 DBG][MyService]->MyMethod => Update object id: 54
[13:32:47 DBG][MyService]->MyMethod => Update object id: 9
[13:32:50 DBG][MyService]->MyMethod => Update object id: 47
[13:32:51 DBG][MyService]->MyMethod => Update object id: 21
[13:32:51 DBG][MyService]->MyMethod => Update object id: 53
[13:32:53 DBG][MyService]->MyMethod => Update object id: 37
[13:32:55 DBG][MyService]->MyMethod => Update object id: 62
[13:32:55 DBG][MyService]->MyMethod => Update object id: 63
[13:32:56 DBG][MyService]->MyMethod => Update object id: 64
[13:32:56 DBG][MyService]->MyMethod => Update object id: 55
[13:32:56 DBG][MyService]->MyMethod => Update object id: 36
[13:32:56 DBG][MyService]->MyMethod => Update object id: 30
[13:32:56 DBG][MyService]->MyMethod => Update object id: 46
[13:32:56 DBG][MyService]->MyMethod => Update object id: 26
[13:32:56 DBG][MyService]->MyMethod => Update object id: 56
[13:33:00 DBG][MyService]->MyMethod => Update object id: 29
[13:33:00 DBG][MyService]->MyMethod => Update object id: 57
[13:33:00 DBG][MyService]->MyMethod => Update object id: 42
[13:33:01 DBG][MyService]->MyMethod => Update object id: 38
[13:33:01 DBG][MyService]->MyMethod => Update object id: 32
[13:33:01 DBG][MyService]->MyMethod => Update object id: 48
[13:33:01 DBG][MyService]->MyMethod => Update object id: 40
[13:33:01 DBG][MyService]->MyMethod => Update object id: 31
[13:33:01 DBG][MyService]->MyMethod => Update object id: 49
[13:33:02 DBG][MyService]->MyMethod => Update object id: 58
[13:33:02 DBG][MyService]->MyMethod => Update object id: 9
[13:33:02 DBG][MyService]->MyMethod => Update object id: 33
[13:33:02 DBG][MyService]->MyMethod => Update object id: 41
[13:33:04 DBG][MyService]->MyMethod => Update object id: 50
[13:33:04 DBG][MyService]->MyMethod => Update object id: 34
[13:33:06 DBG][MyService]->MyMethod => Update object id: 17
[13:33:06 DBG][MyService]->MyMethod => Update object id: 59
[13:33:07 DBG][MyService]->MyMethod => Update object id: 45
[13:33:08 DBG][MyService]->MyMethod => Update object id: 61
[13:33:08 DBG][MyService]->MyMethod => Update object id: 54
[13:33:08 DBG][MyService]->MyMethod => Update object id: 43
[13:33:08 DBG][MyService]->MyMethod => Update object id: 51
[13:33:08 DBG][MyService]->MyMethod => Update object id: 52

The intervals between processing subsequent objects are quite large. I thought that my asynchronius code would make the method for each object to be called at the same time (or very, very similar)...

How can I make sure my code is asynchronous and working properly? Can you confirm that I'm calling tasks for each object correctly?


Solution

  • Parallel.ForEach will just not aggressively liberate threads from the thread pool. The Task scheduler will decide how many threads you ought to be using, and depending on cores, what your system is already doing and the heuristics it's gleaned from your tasks, it might not be doing what you'd expect.

    However, you have bigger conceptual issues here. You are using Parallel.ForEach (for what looks to be) bread-and-butter IO bound work, which is tying-up/blocking threads that can be more suitably offloaded to IO Completion ports, and in-turn has a greater chance to achieve more concurrent IO Bound workloads.

    In short Parallel.ForEach is just not suitable for IO bound workloads, it's optimised for CPU bound workloads and does not support the async and await pattern.

    In summary, you should let your IO methods be async (all the way down) and using a more suitable technology that can deal with the async and await pattern, like Task.WhenAll or TPL DataFlow ActionBlock<T> or Reactive Extensions. Additionally, you should likely batch your work to the database so you aren't thrashing that either. This will give you the best chance of your workloads not sitting around blocking the thread pool threads, and in-turn you will likely find the through-put (in general) will be much higher.