Dezyne: From dzn.async via Defer to Async
Introduction
Sometimes a synchronous interface needs to be converted into an asynchronous interface. The way to do this in Dezyne from Verum has changed over time. I will first introduce two interfaces, one asynchronous and one synchronous. Then I will show different ways to make a component that provides the synchronous and requires the asynchronous interfaces.
Unless stated otherwise, all examples are made using Dezyne 2.18.
From Synchronous to Asynchronous Calls
One of the often needed behavioral transformations is converting a synchronous interface to an asynchronous one. For the examples below I introduce two interfaces.
The ExampleControl
has a Start
in-event which can succeed or fail, indicated by an asynchronous OnOk
or OnFail
out-event.
|
|
The Example
interface is the synchronous counterpart where the result of Start
is given by the reply value of Result.Ok
or Result.Fail
.
|
|
The x
boolean defined on line 11 is used in the guards of Start
to indicate non-determinism. Since x
is always true
, a [x]
guard is the same as a [true]
guard. I think it looks nice in the interface description to use the [x]
guards. It mimics a checkbox list.
Dezyne 2.16: dzn.async
and Why Not to Use It
In Dezyne 2.16 and before there was an internal dzn.async
interface that could be used to create asynchronous behavior. It could pass along parameters and the ack
out-event had a higher priority than other events. The effect was that the ack
out-event would be processed before and not be interrupted by other events.
Below is the definition of the dzn.async
interface in pseudo code. The formals
on line 3 represents an optional list of external
parameters. On line 15 the immediately
trigger is used to indicate the higher priority of the ack
out-event.
|
|
Below is shown how to use dzn.async
. It has been made using Dezyne 2.16. In this example we do not use the passing of parameters (formals). To not have to keep track of the result of the base.Start()
call we do that call in the ack
out-event handler.
|
|
Below is an image of a trace of this component. Here we see a downside of using dzn.async
. It is not clear from the trace when req
or ack
are triggered, or even the existence of this internal component. You can see that the call to Start
and the answer (OnOk
or OnFail
) are decoupled / asynchronous.
The major advantage of dzn.async
is not demonstrated in this example. That is that that the ack
event has a higher priority than normal events. The effect of this is that the ack
events are received before other events and that no behavior will have to be defined for those other events. This can reduce the amount of code that needs to handle the different interleavings of events.
This advantage was also the main disadvantage. The semantics is not compatible with the blocking
feature. The blocking
feature is used to convert asynchronous behavior back to synchronous behavior. Therefor dzn.async
was deprecated in Dezyne 2.16 and removed in Dezyne 2.17. But even if you use Dezyne 2.16 or lower, I advice never to use this feature and in stead use the Async
solution from this article.
Using defer
Starting from Dezyne 2.16 the defer
keyword has been added. The commands specified in the code block after defer
will be executed asynchronously after the current execution finishes. The code block is put in a queue and will be executed in turn. When the state of the component changes, the execution of the supplied code block will be canceled. For more complex cases you can specify which state variables will trigger this. When using defer
you have to be mindful about state changes.
|
|
A useful feature of the defer
is that it can capture variables values as can be seen on line 13. Here the res
local variable is captured and evaluated at a later moment when the block is being executed.
In the simulator we can see when there are actions ready in the defer queue. The defer button will be highlighted. But when looking at the trace as a whole it’s less clear which events were generated by deferred executions. Defer can also not monitor shared state variables, meaning sometimes you will have to introduce a state variable just to be able to cancel a defer. This is also the reason for the starting
state variable in the example.
Remake of the Async Interface and Component
I liked the way that dzn.async
worked. I am using my own version of it defined as follows:
|
|
This is used to make synchronous calls asynchronous. It is a limited in that it doesn’t pass along any parameters and it doesn’t have the high priority ack
out-event. But it is fully compatible with the use of blocking
. By utilizing defer
a component that implements this interface can be made. A possible implementation is shown below.
|
|
This uses the implicit interface constraints and defer features. When the state of a component changes, outstanding defers are canceled. When calling clr
the boolean toggle
is toggled, thereby changing the state and cancelling the defer.
We don’t always want to use defer
for the implementation. Maybe because we don’t want to use the dzn::pump
, we have our own handling of the threading environment or want to run purely single-threaded. Then a foreign component can be used. This makes using the Async
interface usable in many different situations.
Using Async
Interface
Using the Async
interface we can implement our example as follows. We use the same approach as for the ExampleControlDznAsync
but now we can also use the implicit interface constraints feature, eliminating the need to track the state in the component.
|
|
What can be seen in the trace is that the use of the Async
interface is very explicit. With this approach you can exactly track the state of the Async interface at any time, which is very helpful for debugging problems in more complex components.
The cancellation of an outstanding req
must be done explicitly. The verifier will help to make sure we do not forget to cancel it when needed.
One of the downsides is that we need to provide an implementation of the Async
interface. It is best to create a system component that will instantiate the needed helper-components.
|
|
Conclusion
Do not use dzn.async
. If you still do, please replace it with the Async
alternative described above.
Defer is an easy out of the box solution with some nice properties. But it needs the dzn::pump
and can be trickier to use for bigger components.
The Async
interface solution is very versatile and explicit, but less flexible. It can be implemented using defer
, but for special cases other implementations using foreign components can be made. It is also possible to use it in unittests, because the interface is mockable.