This is the second in a series of blog posts on creating an asynchronous D-Bus service in python. For part 1, go here.
Last time we created a base for our asynchronous D-Bus service with a simple synchronous server/client. In this post, we’ll start from that base which can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1. Of course, all of today’s code can be found in the same project with the part2 tag: https://github.com/larryprice/python-dbus-blog-series/tree/part2.
Why Asynchronous?
Before we dive into making our service asynchronous, we need a reason to make our service asynchronous. Currently, our only d-bus object contains a single method, quick
, which lives up to its namesake and is done very quickly. Let’s add another method to RandomData
which takes a while to finish its job.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Note the addition of the slow
method on the RandomData
object. slow
is a contrived implementation of building an n-bit random number by concatenating 1s and 0s, sleeping for 1 second between each iteration. This will still go fairly quickly for a small number of bits, but could take quite some time for numbers as low as 16 bits.
In order to call the new method, we need to modify our client
binary. Let’s add in the argparse
module and take in a new argument: --slow
. Of course, --slow
will instruct the program to call slow
instead of quick
, which we’ll add to the bottom of the program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Now we can run our client
a few times to see the result of running in slow mode. Make sure to start or restart the service
binary before running these commands:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Your mileage may vary (it is a random number generator, after all), but you should eventually see a similar crash which is caused by a timeout in the response of the D-Bus server. We know that this algorithm works; it just needs more time to run. Since a synchronous call won’t work here, we’ll have to switch over to more asynchronous methods…
An Asynchronous Service
At this point, we can go one of two ways. We can use the threading
module to spin threads within our process, or we can use the multiprocessing
module to create child processes. Child processes will be slightly pudgier, but will give us more functionality. Threads are a little simpler, so we’ll start there. We’ll create a class called SlowThread
, which will do the work we used to do within the slow
method. This class will spin up a thread that performs our work. When the work is finished, it will set a threading.Event
that can be used to check that the work is completed. threading.Event
is a cross-thread synchronization object; when the thread calls set
on the Event
, we know that the thread is ready for us to check the result. In our case, we call is_set
on our event to tell a user whether or not our data is ready.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
On the RandomData
object itself, we’ll initialize a new thread tracking list called threads
. In slow
, we’ll initialize a SlowThread
object, append it to our threads
list, and return the thread identifier from SlowThread
. We’ll also want to add a method to try to get the result from a given SlowThread
called slow_result
, which will take in the thread identifier we returned earlier and try to find the appropriate thread. If the thread is finished (the event
is set), we’ll remove the thread from our list and return the result to the caller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Last thing we need to do is to update the client to use the new methods. We’ll call slow
as we did before, but this time we’ll store the intermediate result as the thread identifier. Next we’ll use a while loop to spin forever until the result is ready.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Note that this is not the smartest way to do this; more on that in the next post. Let’s give it a try!
1 2 3 4 5 6 7 8 |
|
Next time
This polling method works as a naive approach, but we can do better. Next time we’ll look into using D-Bus signals to make our client more asynchronous and remove our current polling implementation.
As a reminder, the end result of our code in this post is MIT Licensed and can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part2.