Ho rubato la risposta di kindall e l'ho ripulita un po '.
La parte chiave è l'aggiunta di * args e ** kwargs a join () per gestire il timeout
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
RISPOSTA AGGIORNATA QUI SOTTO
Questa è la mia risposta più popolare, quindi ho deciso di aggiornare con il codice che verrà eseguito su py2 e py3.
Inoltre, vedo molte risposte a questa domanda che mostrano una mancanza di comprensione riguardo a Thread.join (). Alcuni non riescono completamente a gestire l' timeoutarg. Ma c'è anche un caso angolare di cui dovresti essere consapevole riguardo alle istanze quando hai (1) una funzione target che può tornare Nonee (2) passi anche l' timeoutarg a join (). Vedere "TEST 4" per comprendere questo caso d'angolo.
Classe ThreadWithReturn che funziona con py2 e 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
Di seguito sono riportati alcuni test di esempio:
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))
Riesci a identificare il caso angolare che potremmo incontrare con TEST 4?
Il problema è che ci aspettiamo che giveMe () restituisca None (vedi TEST 2), ma prevediamo anche che join () restituisca None in caso di timeout.
returned is None significa:
(1) ecco cosa restituito da GiveMe (), oppure
(2) join () scaduto
Questo esempio è banale poiché sappiamo che giveMe () restituirà sempre None. Ma nell'istanza del mondo reale (in cui il bersaglio potrebbe legittimamente restituire Nessuno o qualcos'altro) vorremmo verificare esplicitamente l'accaduto.
Di seguito è come affrontare questo caso d'angolo:
# 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]L'ordine verrà mantenuto e l'uscita dawithconsentirà la raccolta dei risultati.[f.result() for f in futures]