Tasks vs. TPL Dataflow vs. Async/Await, which to use when?
Asked Answered
L

2

12

I have read through quite a number technical documents either by some of the Microsoft team, or other authors detailing functionality of the new TPL Dataflow library, async/await concurrency frameworks and TPL. However, I have not really come across anything that clearly delineates which to use when. I am aware that each has its own place and applicability but specifically I wonder in regards to the following situation:

I have a data flow model that runs completely in-process. At the top sits a data generation component (A) which generates data and passes it on either via data flow block linkages or through raising events to a processing component (B). Some parts within (B) have to run synchronously while (A) massively benefits from parallelism as most of the processes are I/O or CPU bound (reading binary data from disk, then deserializing and sorting them). In the end the processing component (B) passes on transformed results to (C) for further usage.

I wonder specifically when to use tasks, async/await, and TPL data flow blocks in regards to the following:

  • Kicking off the data generation component (A). I clearly do not want to lock the gui/dashboard thus this process would have to somewhat run on a different thread/task.

  • How to call methods within (A), (B), and (C) that are not directly involved in the data generation and processing process but perform configuration work that may possibly take several hundred milliseconds/seconds to return. My hunch is that this is where async/await shines?

  • The most I struggle with is how to best design the message passing from one component to the next. TPL Dataflow looks very interesting but it is sometimes too slow for my purpose. (Note at the end in regards to performance issues). If not using TPL Dataflow how do I achieve responsiveness and concurrency by in-process inter-task/concurrent data passing? Example, clearly if I raise an event within a task the subscribed event handler runs in the same task instead of being passed to another task, correct? In summary, how can component (A) go about its business after passing on data to component (B) while component (B) retrieves the data and focuses on processing it? Which concurrency model is best used here? I implemented data flow blocks here, but is that truly the best approach?

  • I guess above points in summary point to my struggle with how to design and implement API type components using standard practice? Should methods be designed async, data inputs as data flow blocks, and data output as either data flow block or event? What is the best approach in general? I am asking because most of the components mentioned above are supposed to work independently, so they can essentially be swapped out or independently altered internally without having to re-write accessors and output.

Note on performance: I mentioned TPL Dataflow blocks are sometimes slow. I deal with a high throughput, low latency type of application and target disk I/O limits and thus tpl dataflow blocks often performed much slower than, for example, a synchronous processing unit. Issue is that I do not know how to embed the process in its own task or concurrent model to achieve something similar than what tpl dataflow blocks already take care of, but without the overhead that comes with tpl df.

Ladysmith answered 27/11, 2012 at 6:25 Comment(4)
“What is the best approach in general?” I think there isn't one. Programming is often about considering alternatives, and there isn't one that's clearly best in all cases. That's also why when you care about performance, you should use profiling, there is no “X is always better than Y”.Goodhen
@svick, with all the respect you to your expertise and opinion, but that is why I specified a use case and think asked specific, targeted questions when which approach is most suitable. Would you mind sharing your knowledge how you would handle each of the 4 bullet points? Thanks a lotLadysmith
Feel free to bring it up the closure on Meta Stack Overflow.Heraclitus
@Freddy No, because you're disagreeing in the wrong place. This is the first (of two times so far) that you've been pointed to Meta Stack Overflow. Comments are for clarification of the question. They're not for meta commentary (should the question be open, closed, etc).Heraclitus
N
12

It sounds like you have a "push" system. Plain async code only handles "pull" scenarios.

Your choice is between TPL Dataflow and Rx. I think TPL Dataflow is easier to learn, but since you've already tried it and it won't work for your situation, I would try Rx.

Rx comes at the problem from a very different perspective: it is centered around "streams of events" rather than TPL Dataflow's "mesh of actors". Recent versions of Rx are very async-friendly, so you can use async delegates at several points in your Rx pipeline.

Regarding your API design, both TPL Dataflow and Rx provide interfaces you should implement: IReceivableSourceBlock/ITargetBlock for TPL Dataflow, and IObservable/IObserver for Rx. You can just wire up the implementations to the endpoints of your internal mesh (TPL Dataflow) or query (Rx). That way, your components are just a "block" or "observable/observer/subject" that can be composed in other "meshes" or "queries".

Finally, for your async construction system, you just need to use the factory pattern. Your implementation can call Task.Run to do configuration on a thread pool thread.

Natoshanatron answered 27/11, 2012 at 13:1 Comment(2)
When would you choose use a "push" system over just writing the methods that process the data after the async methods returns a result? I opened a question here after reading few posts of yours where you recommend using dataflow but I'm still not sure I fully understand the benefits over just adding a process method after awaiting for async tasks, could you please elaborate or reference to the sample I wrote in that question?Viole
Most systems are "push" or "pull" by nature, and require translation (background process / buffer) if you want to use them the other way. E.g., retrieving records from a database is a "pull" system; user input is a "push" system. I prefer to use whichever approach is more natural for the situation at hand; e.g., use async for database queries and use events/Rx for responding to UI input.Natoshanatron
K
0

Just wanted to leave this here, if it helps someone to get a feeling when to use dataflow, because I was surprised at the TPL Dataflow performance. I had a the next scenario:

  • Iterate through all the C# code files in project (around 3500 files)
  • Read all the files lines (IO operation)
  • Iterate through all the file lines and find some strings in them
  • Return the files and their lines which have a the searched string in

I thought that this was a really good example for the TPL Dataflow but when I have just generated a new Task for each file which I needed to open, and done all the logic in that specific task, this code was faster.

From this my conclusion was to go with Await/Async/Task implementation by default, at least for such simple tasks and that TPL Dataflow was made for more complex situations, especially when you would need Batching and other more "pushed" based scenarios and when the synchronization is more of an issue.

Edit: Then I have done some more reasearch on this and created a demo project and the results are quite interesting. Because as when we have more operations and as they become more complex, the TPL Dataflow becomes more efficient.

Here is the link to the repo.

Korean answered 7/7, 2022 at 9:46 Comment(3)
This is not a useful answer, because it doesn't include the code of the specific experiment. My speculation is that the tasks in the pipeline were not coarse grained, as mentioned in the first paragraph of the documentation. If you are passing around a vast number of small strings, and only doing a trivial calculation with each string, then the overhead is going to be huge. This is easily solvable by chunkifying the workload, for example with the Chunk LINQ operator or a BatchBlock<T>.Crap
@TheodorZoulias I think I didn't do anything wrong :), but you can now check the demo project if you are interested and check it out for yourself. And thanks for the head ups :), that the initial reply wasn't reliable enough.Korean
Questions and answers here are expected to autonomous. External links become dead links over time, and the questions/answers that contain them lose most of their value. Could you include in the answer a minimal example of a TPL Dataflow that reproduces your observations? You should be able to concoct a trivial example with 40-50 lines of code max, that showcases the essence of your experiment, and can be reviewed with minimal effort from members of the community.Crap

© 2022 - 2024 — McMap. All rights reserved.