Activity diagrams are used to model the behaviors of a system, and the way in which these behaviors are related in an overall flow of the system. In simple terms, they show system-wide and component-wide workflows.
This post introduces an approach for modeling parallel applications by utilizing activity diagrams to show parallel workflows and point out the places at which they get synchronized, and using class diagrams and sequence diagrams to put together the complete parallel puzzle.
Showing parallel workflows via activity diagrams is great in the early stages of the design, where there’s almost no respect to classes and there’s no need to show who does what. However, when entering to the detail stage we’ll often want to see more then the activity diagrams are willing to revile. At this point we’ll need the sequence diagrams and the class diagrams to complete the picture, as we’ll see in the case study further on.
The ‘Fork’ element is used to show that actions execute in parallel. In the figure below, Action1 calls Action2 and Action3 in parallel.
Using expansion regions we can show that sequence of actions (activity) may execute concurrently.
If you do parallelism, you'll probably need to synchronize. The ‘Join’ element is used to show that a certain action will not execute until some other actions are done. In the figure below, action 3 will not execute until Action1 and Action2 are done.
Case Study – Camera Picture Processing
Let’s see how the activity diagram can be used to model the parallel behavior of a picture processing application. The application supports different kinds of cameras (Bulet, Dome etc), the cameras periodically send picture streams, which the appropriate agent receives and analyzes as appropriate. We will focus on the Dome camera that analyzes the stream in 3 phases.
The activity diagram below shows that the phases of the analysis run in parallel. The 1st and the 2nd phases run concurrently, while the 3nd phase starts only after the 2nd phase complete, on top of that, only after all 3 phases are done the analysis process complete and the results get published .
We need more!
The thing with activity diagrams is that they don’t convey which class is responsible for each action. This is quite alright in the first stages of the design, but as we move forward with the design we also want to see which object initiate the parallel action, which object is synchronized with which, and in some cases we even want to get some information about the thread that initiate the activity, is it a thread-pool thread, is it some background thread that we’ve created to do some periodic work, is it the main thread etc.
Using Activity Diagram with Partitions
Partition elements can be used to divide the diagram in order to show which action belong to which class.
Using Class Diagram
So we get that the Gateway is responsible to the 2nd phase and the DomeCameraAgent is responsible to the 1st and the 3rd, but merely seeing that is not enough.
The class diagram below shows that the DomeCameraAgent composes the objects responsible for the 1st and 3rd phases, and that the 2nd phase actually runs in a separate node that is reachable through a gateway that is also used by other cameras.
Using Sequence Diagram (High Level Design)
The sequence diagram below is very similar to the activity diagram shown above, but now we see that the entire 3 phase analysis is eligible to run in parallel (as the ‘par’ Operator indicates), which consequently rules that DomeCameraAgent and its parts must be thread-safe, and so does the Gateway (we already suspected that it should after we realized that it is used by other agents). We use timing constraints to show that the overall analysis ends and the results are published only after both 1st and 3rd phases complete.
Using Sequence Diagram (Detail Design)
Way to Go Parallel
Now let’s get into details, first let’s see how DomeCameraAgent makes things go parallel.
The sequence above shows that we choose to scale up via Task Parallel Library (TPL). At some early point, DomeCameraAgent creates custom scheduler with the appropriate concurrency level and priority. When picture stream arrives - TCPClient calls ‘RunAnalysis’ on the DomeCameraAgent, the latter schedule the analysis callback via TPL instructing it to use the custom scheduler. In turn, TPL execute the callback in a scalable fashion. Setting the concurrency level to say…3 and setting the priority to Low- will yield up to 3 analyses running in parallel in low priority. This may or may not make sense, but at least now we can present the design and get some feedback.
Way to Synchronize
The 1st act of synchronization is to make sure the 3rd phase will run only after the 2nd, this is easy, we simply run the 2nd phase synchronously from the context of the worker thread, when it is done, we go a head and run the 3rd phase. The second act is to wait for the 1st and the 3rd phases, this requires some actual synchronization, we use the CountdownEvent class (new to .NET Framework 4.0) that is signaled when its count reaches zero, we set its initial count to 2, execute the analysis phases and wait for the event to signal. When the 1st phase complete it signals the event therefore decrementing its count, and the same goes for the 3st phase. No matter which phase completed first, or even if the 1st phase had managed to signal before the wait began– the event will be released from the wait only after both (thus all) phases are done.