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.
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
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
RandomData object itself, we’ll initialize a new thread tracking list called
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
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
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.