gh-5225225/py3line — pull request #2 by nathan-hoad (2014-02-21 07:47:01+00:00)
Title: Get py3bar running under asyncio, with a non-blocking block_subprocess.

Hi! I figured that showing running code would be the best way to show you how this would work :)

This pull request gets py3line running roughly the same way as my panel_bar.py script, allowing the bar to update when necessary, and preventing any slow processes from blocking the rest of the bar from updating.

So... here's what the problems were with the code.

1. You weren't running things under the event loop. Asyncio, and a vast majority of other asynchronous frameworks have an "event loop", basically a `while True` that checks for events, like readable/writable sockets, timed calls, things like that. With asyncio, you need to explicitly run this event loop, preferably with a coroutine, so that the event loop has a handle on when to stop.

That's what the `asyncio.get_get_event_loop().run_until_complete(main())` is doing - instructing asyncio to run the event loop until the `main()` function finishes. In this case, the main function shouldn't ever finish, unless an error occurs, because of the `while True` contained within it.

2. `yield from` should only be used on asynchronous operations in asyncio code, like running a subprocess, or interacting with a socket in some way. `json.dumps(obj)` isn't an asynchronous operation - when you call it, it will block until it can return the serialized object. Here's are some examples of when you should use `yield from` in asyncio.

# will sleep for 60 seconds, allowing other operations to run by 'yielding' back to the event loop.
yield from asyncio.sleep(60)

# allow other operations to run until we've completed connecting to mpd
reader, writer = yield from asyncio.open_connection(host='localhost', port=6600)

# asynchronously read from mpd socket
okay = yield from reader.read(2**12)

assert okay == b"OK MPD 0.16.0\n"

writer.write(b"currentsong\n")

# the above write may have been buffered, so this will wait until it's
# finished writing. Non-blocking though, of course.
yield from writer.drain()

Conceptually, asynchronous vs synchronous code can be quite confusing. If you don't quite understand what I mean by above, let me know and I'll try to rephrase it. :)

So those explain the changes in part one. Part two, I changed the code such that each block could run independently, allowing the bar to be updated on demand. This basically works by using a global `asyncio.Queue()`, which blocks put a sentinel into when they want the bar to be updated. The main function coroutine waits on this queue asynchronously, and when it receives an object from the queue, it will iterate over all the blocks and rerender with the given cachestrs.

By using a queue, this basically does updates on demand - it will only update when one of the blocks wants it to, i.e. there are changes. If, instead, you wanted to update every second regardless, you could simply remove the global queue and make the main coroutine do an `asyncio.sleep(1)` instead. Thinking about it now, possibly using `asyncio.Event()` would be a better choice than `asyncio.Queue()`, but that's neither here nor there.

I hope that explains how it all works. If not, feel free to ask any questions necessary, I'm here to help :)