Ukradłem odpowiedź życzliwą i trochę ją posprzątałem.
Kluczową częścią jest dodanie * args i ** kwargs do join () w celu obsłużenia limitu czasu
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
ZAKTUALIZOWANO ODPOWIEDŹ PONIŻEJ
To moja najpopularniejsza odpowiedź, więc postanowiłem zaktualizować kod, który będzie działał zarówno na py2, jak i py3.
Dodatkowo widzę wiele odpowiedzi na to pytanie, które pokazują brak zrozumienia odnośnie Thread.join (). Niektóre całkowicie nie radzą sobie z timeout
arg. Ale jest też przypadek narożny, o którym powinieneś wiedzieć, jeśli masz (1) funkcję docelową, która może zwrócić None
i (2) przekazujesz również timeout
argument arg, aby dołączyć (). Zobacz „TEST 4”, aby zrozumieć ten przypadek narożny.
Klasa ThreadWithReturn, która działa z py2 i 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
Niektóre przykładowe testy pokazano poniżej:
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))
Czy potrafisz zidentyfikować przypadek, w którym możemy spotkać się z TESTEM 4?
Problem polega na tym, że oczekujemy, że metoda returnMe () zwróci None (patrz TEST 2), ale spodziewamy się również, że funkcja join () zwróci None, jeśli upłynie limit czasu.
returned is None
oznacza albo:
(1) właśnie to zwrócił returnMe () lub
(2) Upłynął limit czasu dołączenia ()
Ten przykład jest trywialny, ponieważ wiemy, że giveMe () zawsze zwróci None. Ale w rzeczywistej instancji (gdzie cel może zgodnie z prawem zwrócić Brak lub coś innego) chcielibyśmy wyraźnie sprawdzić, co się stało.
Poniżej znajduje się sposób rozwiązania tego przypadku:
# 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]
Kolejność zostanie utrzymana, a zamknięciewith
umożliwi zbieranie wyników.[f.result() for f in futures]