asyncio and blocking - an update
frank at chagford.com
Tue Feb 9 02:33:49 EST 2016
Some of you may have been following my attempts to modify my asyncio app so
that it does not block when accessing the database. Here is an update.
I came up with what felt like a good idea. Run the database handler in a
separate thread, pass requests to it using a queue.Queue, and get it to pass
results back using an asyncio.Queue.
It works, but I had a vague sense that performance was a bit sluggish, so I
tried the 'recommended' approach of using asyncio.run_in_executor() to
execute database calls in a separate thread. It felt a bit faster.
Now I have written a proper timing test, and the recommended approach is
much faster. I am not 100% sure of the reason, but I think the problem is
that, with my method, when the database tries to 'put' a row on the return
queue, it has to use 'loop.call_soon_threadsafe()', and this seems to create
It would not matter so much if I used cur.fetchall(), and sent all the rows
back in one 'put', but I really wanted to iterate over the cursor and 'put'
a row at a time. The idea was that it would be truly asynchronous from end
to end - the database handler would retrieve rows one at a time, and via the
queue my app would process them one at a time, all without blocking.
I can switch to fetchall(), but then there is no benefit over the
recommended approach. Although fetchall() is non-blocking, once the rows are
received as a list the function that processes them effectively does block.
If that became a problem one could use an Asynchronous Iterator to process
the list, but I have not had that need yet.
Writing timing tests is tricky. It is possible that under some
circumstances, with a heavy load, a large table, and a time-consuming
function to process each row, the overhead of call_soon_threadsafe() would
be minimised and my approach might be effective. For now, however, I will
regretfully abandon my approach and stick with run_in_executor().
More information about the Python-list