Python - What is queue.task_done() used for? Python - What is queue.task_done() used for? python python

Python - What is queue.task_done() used for?


Queue.task_done is not there for the workers' benefit. It is there to support Queue.join.


If I give you a box of work assignments, do I care about when you've taken everything out of the box?

No. I care about when the work is done. Looking at an empty box doesn't tell me that. You and 5 other guys might still be working on stuff you took out of the box.

Queue.task_done lets workers say when a task is done. Someone waiting for all the work to be done with Queue.join will wait until enough task_done calls have been made, not when the queue is empty.


eigenfield points out in the comments that it seems really weird for a queue to have task_done/join methods. That's true, but it's really a naming problem. The queue module has bad name choices that make it sound like a general-purpose queue library, when it's really a thread communication library.

It'd be weird for a general-purpose queue to have task_done/join methods, but it's entirely reasonable for an inter-thread message channel to have a way to indicate that messages have been processed. If the class was called thread_communication.MessageChannel instead of queue.Queue and task_done was called message_processed, the intent would be a lot clearer.

(If you need a general-purpose queue rather than an inter-thread message channel, use collections.deque.)


.task_done() is used to mark .join() that the processing is done.

💡 If you use .join() and don't call .task_done() for every processed item, your script will hang forever.


Ain't nothin' like a short example;

import loggingimport queueimport threadingimport timeitems_queue = queue.Queue()running = Falsedef items_queue_worker():    while running:        try:            item = items_queue.get(timeout=0.01)            if item is None:                continue            try:                process_item(item)            finally:                items_queue.task_done()        except queue.Empty:            pass        except:            logging.exception('error while processing item')def process_item(item):    print('processing {} started...'.format(item))    time.sleep(0.5)    print('processing {} done'.format(item))if __name__ == '__main__':    running = True    # Create 10 items_queue_worker threads    worker_threads = 10    for _ in range(worker_threads):        threading.Thread(target=items_queue_worker).start()    # Populate your queue with data    for i in range(100):        items_queue.put(i)    # Wait for all items to finish processing    items_queue.join()    running = False


"Read the source, Luke!" -- Obi-one Codobi

The source for ayncio.queue is pretty short.

  • the number of unfinished tasks goes up by one when you put to the queue.
  • it goes down by one with you call task_done
  • join() awaits there being no unfinished tasks.

This makes join useful if and only if you are calling task_done(). Using the classic bank analogy:

  • people come in the doors and get in line; door is a producer doing a q.put()
  • when a teller is idle and a person is in line, they go to the teller window. teller does a q.get().
  • When the teller has finished helping the person, they are ready for the next one. teller does a q.task_done()
  • at 5 p.m., the doors are locked door task finishes
  • you wait until both the line is empty and each teller has finished helping the person in front of them. await q.join(tellers)
  • then you send the tellers home, who are now all idling with an empty queue. for teller in tellers: teller.cancel()

Without the task_done(), you cannot know every teller is done with people. You cannot send a teller home while they have a person at his or her window.