Robé la respuesta de Kindall y la limpié un poco.
La parte clave es agregar * args y ** kwargs a join () para manejar el tiempo de espera
class threadWithReturn(Thread):
def __init__(self, *args, **kwargs):
super(threadWithReturn, self).__init__(*args, **kwargs)
self._return = None
def run(self):
if self._Thread__target is not None:
self._return = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
def join(self, *args, **kwargs):
super(threadWithReturn, self).join(*args, **kwargs)
return self._return
RESPUESTA ACTUALIZADA A CONTINUACIÓN
Esta es mi respuesta popularmente votada, así que decidí actualizar con un código que se ejecutará tanto en py2 como en py3.
Además, veo muchas respuestas a esta pregunta que muestran una falta de comprensión con respecto a Thread.join (). Algunos fallan completamente en manejar el timeout
argumento. Pero también hay un caso de esquina que debe tener en cuenta con respecto a las instancias en las que tiene (1) una función de destino que puede devolver None
y (2) también pasa el timeout
argumento para unirse (). Consulte "PRUEBA 4" para comprender este caso de esquina.
Clase ThreadWithReturn que funciona con py2 y py3:
import sys
from threading import Thread
from builtins import super # https://stackoverflow.com/a/30159479
if sys.version_info >= (3, 0):
_thread_target_key = '_target'
_thread_args_key = '_args'
_thread_kwargs_key = '_kwargs'
else:
_thread_target_key = '_Thread__target'
_thread_args_key = '_Thread__args'
_thread_kwargs_key = '_Thread__kwargs'
class ThreadWithReturn(Thread):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._return = None
def run(self):
target = getattr(self, _thread_target_key)
if not target is None:
self._return = target(
*getattr(self, _thread_args_key),
**getattr(self, _thread_kwargs_key)
)
def join(self, *args, **kwargs):
super().join(*args, **kwargs)
return self._return
Algunas pruebas de muestra se muestran a continuación:
import time, random
# TEST TARGET FUNCTION
def giveMe(arg, seconds=None):
if not seconds is None:
time.sleep(seconds)
return arg
# TEST 1
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',))
my_thread.start()
returned = my_thread.join()
# (returned == 'stringy')
# TEST 2
my_thread = ThreadWithReturn(target=giveMe, args=(None,))
my_thread.start()
returned = my_thread.join()
# (returned is None)
# TEST 3
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=2)
# (returned is None) # because join() timed out before giveMe() finished
# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))
¿Puedes identificar el caso de esquina que posiblemente podamos encontrar con TEST 4?
El problema es que esperamos que giveMe () devuelva None (ver PRUEBA 2), pero también esperamos que join () devuelva None si se agota el tiempo de espera.
returned is None
significa ya sea:
(1) eso es lo que giveMe () devolvió, o
(2) unirse () agotó el tiempo de espera
Este ejemplo es trivial ya que sabemos que giveMe () siempre devolverá None. Pero en el caso del mundo real (donde el objetivo puede devolver legítimamente Ninguno u otra cosa), querríamos verificar explícitamente qué sucedió.
A continuación se muestra cómo abordar este caso de esquina:
# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))
if my_thread.isAlive():
# returned is None because join() timed out
# this also means that giveMe() is still running in the background
pass
# handle this based on your app's logic
else:
# join() is finished, and so is giveMe()
# BUT we could also be in a race condition, so we need to update returned, just in case
returned = my_thread.join()
futures = [executor.submit(foo, param) for param in param_list]
El orden se mantendrá y, al salir,with
se permitirá la recopilación de resultados.[f.result() for f in futures]