647 Stimmen

Wie erhält man den Rückgabewert eines Threads in Python?

Die Funktion foo unten gibt eine Zeichenkette zurück 'foo' . Wie kann ich den Wert erhalten 'foo' die vom Ziel des Threads zurückgegeben wird?

from threading import Thread

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

thread = Thread(target=foo, args=('world!',))
thread.start()
return_value = thread.join()

Die oben gezeigte "offensichtliche Lösung" funktioniert nicht: thread.join() zurückgegeben None .

55voto

slow-but-steady Punkte 873

Die meisten Antworten, die ich gefunden habe, sind lang und setzen voraus, dass man mit anderen Modulen oder fortgeschrittenen Python-Funktionen vertraut ist, und werden für jemanden, der nicht bereits mit allem vertraut ist, worüber die Antwort spricht, ziemlich verwirrend sein.

Arbeitscode für einen vereinfachten Ansatz:

import threading

class ThreadWithResult(threading.Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
        def function():
            self.result = target(*args, **kwargs)
        super().__init__(group=group, target=function, name=name, daemon=daemon)

Beispiel-Code:

import time, random

def function_to_thread(n):
    count = 0
    while count < 3:
            print(f'still running thread {n}')
            count +=1
            time.sleep(3)
    result = random.random()
    print(f'Return value of thread {n} should be: {result}')
    return result

def main():
    thread1 = ThreadWithResult(target=function_to_thread, args=(1,))
    thread2 = ThreadWithResult(target=function_to_thread, args=(2,))
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(thread1.result)
    print(thread2.result)

main()

Erläuterung: Ich wollte die Dinge erheblich vereinfachen und habe daher eine ThreadWithResult Klasse und ließ sie erben von threading.Thread . Die verschachtelte Funktion function en __init__ ruft die Thread-Funktion auf, deren Wert wir speichern wollen, und speichert das Ergebnis dieser verschachtelten Funktion als Instanzattribut self.result nachdem die Ausführung des Threads beendet ist.

Das Erzeugen einer Instanz von this ist identisch mit dem Erzeugen einer Instanz von threading.Thread . Übergeben Sie die Funktion, die Sie in einem neuen Thread ausführen wollen, an die target Argument und alle Argumente, die Ihre Funktion benötigen könnte, an die args und alle Schlüsselwortargumente für die kwargs Argument.

z.B..

my_thread = ThreadWithResult(target=my_function, args=(arg1, arg2, arg3))

Ich denke, dies ist wesentlich einfacher zu verstehen als die meisten Antworten, und dieser Ansatz erfordert keine zusätzlichen Importe! Ich habe die time y random Modul, um das Verhalten eines Threads zu simulieren, aber sie sind nicht erforderlich, um die in der Richtlinie geforderte Funktionalität zu erreichen. ursprüngliche Frage .

Ich weiß, dass ich diese Frage erst lange nach dem Zeitpunkt beantworte, zu dem sie gestellt wurde, aber ich hoffe, dass dies in Zukunft mehr Menschen helfen kann!


EDITAR : Ich habe die save-thread-result PyPI-Paket damit Sie auf denselben Code zugreifen und ihn projektübergreifend wiederverwenden können ( GitHub-Code ist hier ). Das PyPI-Paket erweitert vollständig die threading.Thread Klasse, so dass Sie alle Attribute setzen können, die Sie auch bei threading.thread über die ThreadWithResult auch die Klasse!

Die ursprüngliche Antwort oben geht auf die Hauptidee hinter dieser Unterklasse ein, aber für weitere Informationen lesen Sie bitte die eine ausführlichere Erklärung (aus dem Docstring des Moduls) finden Sie hier .

Schnelles Anwendungsbeispiel:

pip3 install -U save-thread-result     # MacOS/Linux
pip  install -U save-thread-result     # Windows

python3     # MacOS/Linux
python      # Windows

from save_thread_result import ThreadWithResult

# As of Release 0.0.3, you can also specify values for
#`group`, `name`, and `daemon` if you want to set those
# values manually.
thread = ThreadWithResult(
    target = my_function,
    args   = (my_function_arg1, my_function_arg2, ...)
    kwargs = {my_function_kwarg1: kwarg1_value, my_function_kwarg2: kwarg2_value, ...}
)

thread.start()
thread.join()
if getattr(thread, 'result', None):
    print(thread.result)
else:
    # thread.result attribute not set - something caused
    # the thread to terminate BEFORE the thread finished
    # executing the function passed in through the
    # `target` argument
    print('ERROR! Something went wrong while executing this thread, and the function you passed in did NOT complete!!')

# seeing help about the class and information about the threading.Thread super class methods and attributes available:
help(ThreadWithResult)

34voto

GuySoft Punkte 1663

Parris / kindall's Antwort join / return Antwort auf Python 3 portiert:

from threading import Thread

def foo(bar):
    print('hello {0}'.format(bar))
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)

        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self):
        Thread.join(self)
        return self._return

twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print(twrv.join())   # prints foo

Hinweis, die Thread Klasse ist in Python 3 anders implementiert.

29voto

user2426679 Punkte 2600

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()

26voto

user341143 Punkte 418

Warteschlange verwenden :

import threading, queue

def calc_square(num, out_queue1):
  l = []
  for x in num:
    l.append(x*x)
  out_queue1.put(l)

arr = [1,2,3,4,5,6,7,8,9,10]
out_queue1=queue.Queue()
t1=threading.Thread(target=calc_square, args=(arr,out_queue1))
t1.start()
t1.join()
print (out_queue1.get())

8voto

Peter Lonjers Punkte 205

Meine Lösung des Problems besteht darin, die Funktion und den Thread in eine Klasse zu verpacken. Erfordert keine Verwendung von Pools, Warteschlangen oder C-Typ-Variablenübergabe. Es ist auch nicht blockierend. Sie prüfen stattdessen den Status. Siehe Beispiel für die Verwendung am Ende des Codes.

import threading

class ThreadWorker():
    '''
    The basic idea is given a function create an object.
    The object can then run the function in a thread.
    It provides a wrapper to start it,check its status,and get data out the function.
    '''
    def __init__(self,func):
        self.thread = None
        self.data = None
        self.func = self.save_data(func)

    def save_data(self,func):
        '''modify function to save its returned data'''
        def new_func(*args, **kwargs):
            self.data=func(*args, **kwargs)

        return new_func

    def start(self,params):
        self.data = None
        if self.thread is not None:
            if self.thread.isAlive():
                return 'running' #could raise exception here

        #unless thread exists and is alive start or restart it
        self.thread = threading.Thread(target=self.func,args=params)
        self.thread.start()
        return 'started'

    def status(self):
        if self.thread is None:
            return 'not_started'
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return 'finished'

    def get_results(self):
        if self.thread is None:
            return 'not_started' #could return exception
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return self.data

def add(x,y):
    return x +y

add_worker = ThreadWorker(add)
print add_worker.start((1,2,))
print add_worker.status()
print add_worker.get_results()

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X