Ich habe die Antwort von kindall geklaut und sie ein wenig aufpoliert.
Der wichtigste Teil ist das Hinzufügen von *args und **kwargs zu join(), um die Zeitüberschreitung zu behandeln
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
AKTUALISIERTE ANTWORT UNTEN
Dies ist meine am häufigsten hochgestimmte Antwort, so dass ich beschlossen, mit Code zu aktualisieren, die sowohl auf py2 und py3 laufen wird.
Außerdem sehe ich viele Antworten auf diese Frage, die ein mangelndes Verständnis für Thread.join() zeigen. Einige versagen komplett bei der Behandlung der timeout
arg. Es gibt aber auch einen Eckfall, den Sie beachten sollten, wenn Sie (1) eine Zielfunktion haben, die Folgendes zurückgeben kann None
und (2) Sie übergeben auch die timeout
arg zu join(). Bitte lesen Sie "TEST 4", um diesen Eckfall zu verstehen.
ThreadWithReturn-Klasse, die mit py2 und py3 funktioniert:
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 target is not 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
Im Folgenden sind einige Testbeispiele aufgeführt:
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))
Können Sie den Eckfall benennen, auf den wir bei TEST 4 möglicherweise stoßen werden?
Das Problem ist, dass wir erwarten, dass giveMe() None zurückgibt (siehe TEST 2), aber wir erwarten auch, dass join() None zurückgibt, wenn es eine Zeitüberschreitung gibt.
returned is None
bedeutet entweder:
(1) das ist, was giveMe() zurückgegeben hat, oder
(2) join() hat sich verzögert
Dieses Beispiel ist trivial, da wir wissen, dass giveMe() immer None zurückgeben wird. Aber in der realen Welt (wo das Ziel legitimerweise None oder etwas anderes zurückgeben kann) würden wir explizit prüfen wollen, was passiert ist.
Im Folgenden wird beschrieben, wie Sie diesen Fall angehen können:
# 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()