This is a question that would probably require a book to answer, but try to narrow the scope.
async
/await
for the composition of methods
You have a system full of small features and excerpts that can be rotated with some level of parallelism. In that situation, instead of filling the code with Begin
/End
, best use async
, await
and Task
for being cleaner and more economical. These commands/class provide an entire infrastructure for command chaining, and even the possibility of code or configuration level choreography.
The jump of the cat there is to remember that they do not solve problems of parallelism, only formalize them. An indirect advantage, as you yourself realized, is that this formalism is more economical than (re)doing all these features at hand, outside that tend to work better than shoving Task behind Task to process without caring about the system boundaries.
Thread
and System.Collections.Concurrent
for control
It may be that the system you are developing needs to operate multi-threaded, but the parallel part is relatively small and/or requires some kind of monitoring or control. Threading hundreds of threads/tasks for later execution (eventual?) can exhaust the system resources, as you yourself saw, outside that makes it difficult to measure progress without some form of coordination or central statistics.
In this situation it may be much more preferable to make expert classes that coordinate queues of items to be processed, and other classes, "processors", that consume data from these queues, feeding other queues or stacking results. Instead of async/await, fixed and hard code.
That one is not a more elegant development line than the first, and is usually a type of premature optimization. The processing of items remains indeterministic (due to parallelism), but the creation of instances of queues and processors passes to determinism, which may be necessary.
Crash and Burn, coordinated transaction or transaction without coordination
Finally, a depends on very important concerns the sense of "large scale" mentioned above.
If it’s a more mathematical type of processing, no side effects, which can simply be re-dispatched in the event of a failure, a system with async
/await
tends to be preferable, as the ease of composition is more important than performance. This is because parallelism tends to increase performance, but it is not magical: from a certain point it can get worse than sequential code, or as you noticed, nor work.
The same for coordinated transactional environments, when processing generates side effects, but where all Apis involved are transactional or co-ordinated (Transactionscope). In these cases it is preferable to first make the code that can run in parallel from the point of view of the transaction than to optimize each reading/writing, but eventually particular passages will be possible to parallelize, and hence async
/await
/Task
will enter the scene normally. Concern is more with collisions/deadlocks arising from parallelism, not parallelism itself.
But there is the rare and sad situation, that you are doing something transactional, with side effects, but have no shared/coordinated transaction at all points. External commands, web services, automation... At these times the focus should be on making paranoid code, which deals well with flaws, partial executions, and reexecutions without duplication, or which at least can detect these situations at the negotiating level. Error handling permeates the code flow in such a way that it stays in sequential practice, and parallelism becomes a secondary concern.
A warning, maybe nothing to do
The advertisement Entity Framework is great, and even hating this API with all my strength, I use it in 100% of my projects involving database. It’s a good API of Unit of work.
What the advertisement fails to mention is that it is an API for Units small and Works non-competitive. EF with long transactions or with many objects is asking to know hell on earth. EF with competition, alone, edge imprestability.
This is a case of a shared resource (database data) where the most recommended API practically only offers the type solution crash and Burn, all or nothing.
This is a still generic answer. Unfortunately it is impossible to go into detail without knowing the case, the nature of the application.
Important to note that the above styles can be used at different points of the system. In my experience the type parallelism async
/await
is most common at the lower levels, when operating on I/O, and at the more abstract, business levels, these commands become much more scarce, parallelism being replaced by objects caching asymptotically generated data, and the use of events to notify progress/complete asynchronous processing.
You are interested in a theoretical example or a real-world discussion. If the theoretical example, the answers are (1) No, this code starts synchronously several asynchronous tasks and (2) Yes. The last question seems more real-world, whose answer begins with a "depends on".
– André LFS Bacci
@Andrélfsbacci, you are correct about the synchronously, I updated the question because that is what I meant. I expect a real-world discussion with theoretical background. The application to be developed is to act in a real world
– Vinícius