Я украл ответ Уиндола и немного его почистил.
Ключевой частью является добавление * args и ** kwargs в join () для обработки времени ожидания
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
ОБНОВЛЕНИЕ ОТВЕТА НИЖЕ
Это мой самый популярный ответ, поэтому я решил обновить код, который будет работать как на py2, так и на py3.
Кроме того, я вижу много ответов на этот вопрос, которые показывают отсутствие понимания в отношении Thread.join (). Некоторые совершенно не справляются с timeoutаргументом. Но есть также угловой случай, о котором вам следует знать в отношении случаев, когда у вас есть (1) целевая функция, которая может возвращать Noneи (2) вы также передаете timeoutаргумент arg для join (). Пожалуйста, смотрите "Тест 4", чтобы понять этот угловой случай.
Класс ThreadWithReturn, который работает с py2 и 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
Некоторые примеры тестов приведены ниже:
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))
Можете ли вы определить угловой случай, с которым мы можем столкнуться с ТЕСТОМ 4?
Проблема в том, что мы ожидаем, что метод giveMe () вернет None (см. ТЕСТ 2), но мы также ожидаем, что join () вернет None, если время ожидания истекло.
returned is None означает либо:
(1) это то, что вернул giveMe (), или
(2) истекло время соединения ()
Этот пример тривиален, так как мы знаем, что метод giveMe () всегда будет возвращать None. Но в случае реального мира (где цель может законно вернуть None или что-то еще), мы бы хотели явно проверить, что произошло.
Ниже описано, как решить этот угловой случай:
# 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]Порядок будет поддерживаться, и выход из негоwithпозволит собирать результаты.[f.result() for f in futures]