J'ai volé la réponse de kindall et l'ai nettoyée un peu.
La partie la plus importante est d'ajouter *args et **kwargs à join() afin de gérer le délai d'attente.
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
RÉPONSE ACTUALISÉE CI-DESSOUS
C'est ma réponse la plus populaire, j'ai donc décidé de la mettre à jour avec un code qui fonctionnera à la fois sur py2 et py3.
De plus, je vois de nombreuses réponses à cette question qui montrent un manque de compréhension concernant Thread.join(). Certaines ne gèrent pas du tout la fonction timeout
arg. Mais il y a également un cas de figure dont vous devez être conscient concernant les cas où vous avez (1) une fonction cible qui peut renvoyer None
et (2) vous passez également l'épreuve timeout
arg à join(). Veuillez consulter le "TEST 4" pour comprendre ce cas particulier.
Classe ThreadWithReturn qui fonctionne avec py2 et py3 :
import sys
from threading import Thread
from builtins import super # https://stackoverflow.com/a/30159479
_thread_target_key, _thread_args_key, _thread_kwargs_key = (
('_target', '_args', '_kwargs')
if sys.version_info >= (3, 0) else
('_Thread__target', '_Thread__args', '_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
Quelques exemples de tests sont présentés ci-dessous :
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))
Pouvez-vous identifier le cas de figure que nous pourrions rencontrer avec TEST 4 ?
Le problème est que nous nous attendons à ce que giveMe() renvoie None (voir TEST 2), mais nous nous attendons également à ce que join() renvoie None s'il s'arrête.
returned is None
signifie soit :
(1) c'est ce que giveMe() a retourné, ou
(2) join() a expiré
Cet exemple est trivial puisque nous savons que giveMe() retournera toujours None. Mais dans les cas réels (où la cible peut légitimement retourner None ou autre chose), nous voudrions vérifier explicitement ce qui s'est passé.
Voici comment aborder ce cas de figure :
# 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()