2

I have a single background process running alongside the main one, where it uses Queue to communicate (using multiprocessing, not multithreading). The main process runs constantly, and the background thread runs once per queue item so that if it gets backlogged, it can still catch up. Instead of closing with the main script (I've enabled daemon for that), I would prefer it to run until the queue is empty, then save and quit.

It's started like this:

q_send = Queue()
q_recv = Queue()
p1 = Process(target=background_process, args=(q_send, q_recv))
p1.daemon = True
p1.start()

Here's how the background process currently runs:

while True:

    received_data = q_recv.get()
    #do stuff

One way I've considered is to switch the loop to run all the time, but check the size of the queue before trying to read it, and wait a few seconds if it's empty before trying again. There are a couple of problems though. The whole point is it'll run once per item, so if there are 1000 queued commands, it seems a little inefficient checking the queue size before each one. Also, there's no real limit on how long the main process can go without sending an update, so I'd have to set the timeout quite high, as opposed to instantly exiting when the connection is broken, and queue emptied. With the background thread using up to 2gb of ram, it could probably do with exiting as soon as possible.

It'd also make it look a lot more messy:

afk_time = 0
while True:

    if afk_time > 300:
        return
    if not q_recv.qsize():
        time.sleep(2)
        afk_time += 2
    else:
        received_data = q_recv.get()
        #do stuff

I came across is_alive(), and thought perhaps getting the main process from current_process() might work, but it gave a picking error when I tried to send it to the queue.

tRuEsAtM
  • 3,517
  • 6
  • 43
  • 83
Peter
  • 3,186
  • 3
  • 26
  • 59

1 Answers1

1

Queue.get has a keyword argument timeout which determines the time to wait for an item if the queue is empty. If no item is available when the timeout elapses then a Empty exception is raised.

Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).

So you can except that error and break out of the loop:

try:
    received_data = q_recv.get(timeout=300)
except queue.Empty:
    return
a_guest
  • 34,165
  • 12
  • 64
  • 118
  • Thanks, if there's no better solutions I'll probably do this, still doesn't get around the 2nd problem I mentioned but will do for now, I'll have to test how frequently things are sent over to get the timeout as low as I can :) – Peter Jun 29 '17 at 20:48
  • @Peter Could you point out your exact goal again? I feel like I missed something (esp. on that 2nd problem). And why do you want to exit the background process at all? What if the main process sends something to the queue later on? – a_guest Jun 29 '17 at 20:54
  • I'm only wanting it to end after the main process has quit, as that means no more queue items will be added. All I need it to do is finishing processing the queue, so the timeout works as a workaround, but the best case scenario would be it'd instantly know the main process has ended and not need any timeout. – Peter Jun 29 '17 at 21:03
  • @Peter In that case you could send a closing command over the queue. Something that - by convention - indicates that the main process finished its work. When the background receives this item it can terminate as well. You just need to come up with a definition of that closing command (for example `None`). – a_guest Jun 29 '17 at 21:08
  • I could do that if I could catch the main process closing, but I'm meaning quitting as in the user closes the window. I looked into executing python code on script exit but didn't have any luck with it. – Peter Jun 29 '17 at 21:16
  • @Peter By "closing the window" you mean the process receives the SIGTERM signal? You can take a look at [this question](https://stackoverflow.com/q/18499497/3767239) for how to react on signals. Alternatively you could create a second "ping" queue over which you send some defined data in regular intervals (using multi-threading in the main process for example). The background process can then use a timeout on that ping queue (with some extra buffer time) and terminate when it doesn't receive a ping anymore. – a_guest Jun 29 '17 at 21:26
  • I looked into signals before, unfortunately while I could get it working with ctrl+c they didn't seem to work when closing the window (I just tried again and had the same result). I considering a third queue for the ping idea, but it seemed a bit too much overhead for something so simple. I think I'll be able to throw it in the current queue without much effort though, and I guess having a short interval of 5 seconds or so won't make much difference to the processing :) – Peter Jun 29 '17 at 22:12
  • According to [this question](https://stackoverflow.com/q/5546223/3767239) SIGHUP is sent when the terminal is closed by force. So it should work when you replace `SIGTERM` with `SIGHUB`. Also the ping queue overhead will be so small that you can safely neglect it. The amount of work for sending and receiving a ping every couple of seconds will likely disappear in the main load. – a_guest Jun 29 '17 at 22:22
  • Unfortunately it appears `SIGHUP` isn't on windows, I just tried all the `SIG___` ones that are included just in case, but none resulted in the code being executed. Turns out I also tried `atexit` before, but again it didn't run with the window closing. I'll get the ping idea hooked up anyway and see how well it works. – Peter Jun 29 '17 at 22:38