Cómo llamar a una función en un hilo Python en ejecución

Digamos que tengo esta clase que genera un hilo:

import threading class SomeClass(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: pass def doSomething(self): pass def doSomethingElse(self): pass 

quiero

 someClass = SomeClass() someClass.start() someClass.doSomething() someClass.doSomethingElse() someClass.doSomething() 

¿Cómo puedo hacer esto? Sé que puedo llamar a una función dentro de la función run() , pero eso no es lo que estoy buscando.

No puedes hacer directamente lo que quieres. El subproceso en segundo plano está ejecutando su función de run , que solo se repite para siempre, por lo que no puede hacer nada más.

Por supuesto, puede llamar a los métodos de la clase en su propio hilo, pero presumiblemente no es lo que quiere aquí.


La razón por la que los marcos como Qt, .NET o Cocoa pueden ofrecer los métodos de tipo runOnOtherThread es que cada subproceso ejecuta un “bucle de eventos”, por lo que todo lo que están haciendo es publicar un evento. Puede hacerlo usted mismo, si reescribe el método de run en un bucle de eventos. Por ejemplo:

 import queue import threading class SomeClass(threading.Thread): def __init__(self, q, loop_time = 1.0/60): self.q = q self.timeout = loop_time super(SomeClass, self).__init__() def onThread(self, function, *args, **kwargs): self.q.put((function, args, kwargs)) def run(self): while True: try: function, args, kwargs = self.q.get(timeout=self.timeout) function(*args, **kwargs) except queue.Empty: self.idle() def idle(self): # put the code you would have put in the `run` loop here def doSomething(self): pass def doSomethingElse(self): pass 

Ahora, puedes hacer esto:

 someClass = SomeClass() someClass.start() someClass.onThread(someClass.doSomething) someClass.onThread(someClass.doSomethingElse) someClass.onThread(someClass.doSomething) 

Si desea simplificar un poco la interfaz de llamada, al costo de más código en la clase, puede agregar métodos de envoltura como este:

  def _doSomething(self): # put the real code here def doSomething(self): self.onThread(self._doSomething) 

Sin embargo, a menos que su método idle tenga trabajo que hacer, realmente está creando el equivalente a un grupo de subprocesos de un solo hilo aquí, y hay formas mucho más fáciles de hacerlo que hacerlo desde cero. Por ejemplo, usando el módulo de futures fuera de PyPI (un puerto trasero del módulo concurrent.futures Python 3):

 import futures class SomeClass(object): def doSomething(self): pass def doSomethingElse(self): pass someClass = SomeClass() with futures.ThreadPoolExecutor(1) as executor: executor.submit(someClass.doSomething) executor.submit(someClass.doSomethingElse) executor.submit(someClass.doSomething) 

O, con sólo el estándar:

 from multiprocessing import dummy as multithreading class SomeClass(object): def doSomething(self): pass def doSomethingElse(self): pass someClass = SomeClass() pool = multithreading.Pool(1) pool.apply(someClass.doSomething) pool.apply(someClass.doSomethingElse) pool.apply(someClass.doSomething) pool.close() pool.join() 

Los pools tienen otras ventajas, y los ejecutores aún más. Por ejemplo, ¿qué sucede si los métodos devuelven valores y desea iniciar dos funciones, luego esperar los resultados y luego iniciar una tercera con los resultados de las dos primeras? Fácil:

 with futures.ThreadPoolExecutor(1) as executor: f1 = executor.submit(someClass.doSomething) f2 = executor.submit(someClass.doSomethingElse) futures.wait((f1, f2)) f3 = executor.submit(someClass.doSomethingElser, f1.result(), f2.result()) result = f3.result() 

Incluso si luego cambia a un grupo de 4 subprocesos, por lo que f1 y f2 pueden estar esperando simultáneamente y f2 puede regresar primero, se le garantiza que doSomethingElser tan pronto como ambos estén terminados, y no antes.


Hay otra posibilidad aquí. ¿Realmente necesita el código para ejecutarse en ese hilo, o simplemente lo necesita para modificar las variables de las que depende el hilo? Si es lo último, simplemente sincronice el acceso a las variables. Por ejemplo:

 class SomeClass(threading.Thread): def __init__(self): self.things_lock = threading.Lock() self.things = [] while True: with self.lock: things = self.things[:] for thing in things: # pass def doSomething(self): with self.lock: self.things.append(0) someClass = SomeClass() someClass.start() someClass.doSomething() 

No hay nada mágico en estar en el hilo principal aquí. Si, además de tener que modificar las variables de las que depende SomeClass , también querría simplemente eliminar algo doSomething del hilo principal para poder hacer cosas más importantes que esperar a que termine, puede crear un hilo adicional de corta duración solo para hacer algo

 someClass = SomeClass() someClass.start() somethingThread = threading.Thread(target=someClass.doSomething) somethingThread.start() doOtherImportantStuffWithSomethingIsHappening() somethingThread.join() 

Busque en concurrent.futures y su método de submit , que hace lo que quiere cuando limita el grupo de subprocesos a un trabajador.