class_docstr: The dispatcher is responsible for assigning tasks to free workers, report results back to the master and
  communicate to the nameserver.
code: def job_runner(self):
      self.runner_cond.acquire()
      while True:
          while (
              self.waiting_jobs.empty() or len(self.idle_workers) == 0
          ):
              self.logger.debug(
                  "DISPATCHER: jobs to submit = %i, number of idle workers = %i -> waiting!"
                  % (self.waiting_jobs.qsize(), len(self.idle_workers))
              )
              self.runner_cond.wait()
              self.logger.debug(
                  "DISPATCHER: Trying to submit another job."
              )
              if self.shutdown_all_threads:
                  self.logger.debug(
                      "DISPATCHER: job_runner shutting down"
                  )
                  self.discover_cond.notify()
                  self.runner_cond.release()
                  return
  
          job = self.waiting_jobs.get()
          wn = self.idle_workers.pop()
  
          worker = self.worker_pool[wn]
          self.logger.debug(
              "DISPATCHER: starting job %s on %s"
              % (str(job.id), worker.name)
          )
  
          job.time_it("started")
          worker.runs_job = job.id
  
          worker.proxy.start_computation(self, job.id, **job.kwargs)
  
          job.worker_name = wn
          self.running_jobs[job.id] = job
  
          self.logger.debug(
              "DISPATCHER: job %s dispatched on %s"
              % (str(job.id), worker.name)
          )
  
imports: import threading
  import logging
  import queue
  import time
  import Pyro4
label_desc: Use image node_id 8 for calling the Dispatcher obj's underlying member method code with example usage: obj.job_runner() without return types