如何在Python中传递和运行回调方法

28
我有一个管理者(主线程),它创建其他线程来处理各种操作。我希望我的管理者在它创建的线程结束时得到通知(当run()方法执行完成时)。
我知道可以使用Thread.isActive()方法检查所有线程的状态来实现,但轮询很糟糕,所以我想要通知。
我考虑给线程提供回调方法,并在run()方法结束时调用此函数:
class Manager():
    ...
    MyThread(self.on_thread_finished).start() # How do I pass the callback

    def on_thread_finished(self, data):
        pass
    ...

class MyThread(Thread):
    ...
    def run(self):
        ....
        self.callback(data) # How do I call the callback?
    ...

谢谢!


2
我对非线程回调方法的示例很感兴趣。 - ThorSummoner
3个回答

25

线程必须拥有管理员的引用才能调用管理员。最简单的方法是在实例化时由管理员将其赋给线程。

class Manager(object):
    def new_thread(self):
        return MyThread(parent=self)
    def on_thread_finished(self, thread, data):
        print thread, data

class MyThread(Thread):

    def __init__(self, parent=None):
        self.parent = parent
        super(MyThread, self).__init__()

    def run(self):
        # ...
        self.parent and self.parent.on_thread_finished(self, 42)

mgr    = Manager()
thread = mgr.new_thread()
thread.start()

如果您想将任意函数或方法分配为回调,而不是存储对管理器对象的引用,那么由于方法包装器等原因,这会变得有些棘手。很难设计回调函数使其获得对管理器线程的引用,这正是您需要的。我尝试了一段时间,但没有想出任何我认为有用或优雅的东西。


2
最后一行不应该是thread.start()吗?否则,没有实际的多线程会发生,而是像任何其他函数一样执行MyThread.run()。但是,如果您实际运行MyThread.start()并创建一个新线程,则self.parent.on_thread_finished(self, 42)仍将在新线程的上下文中执行,而不是在主线程中。您需要某种同步方式,例如Queue,因此@jonathan-lillesæter实际上是正确的。 - iliis
1
刚接触Python。这不是滥用继承吗?MyThread似乎不是Manager的合理子类。 - Josh Noe
4
值得注意的是,使用这个解决方案会在次要线程内执行回调函数,而不是主线程内执行。 另外,在run函数中评估self.parent有什么用?这样做是为了防止parentNone时调用self.parent.on_thread_finished吗?为什么不使用if语句呢?这很令人困惑... - Sindarus

10

这种方法有什么问题吗?

from threading import Thread

class Manager():
    def Test(self):
        MyThread(self.on_thread_finished).start()

    def on_thread_finished(self, data):
        print "on_thread_finished:", data

class MyThread(Thread):
    def __init__(self, callback):
        Thread.__init__(self)
        self.callback = callback

    def run(self):
        data = "hello"
        self.callback(data)

m = Manager()
m.Test() # prints "on_thread_finished: hello"

你不能将本地方法 on_thread_finished 作为回调传递给线程,因为该本地方法需要两个参数。当从线程调用回调时,它只会提供一个参数(数据)。 - Bharathwaaj
10
我知道现在有点晚了,但是对于其他人即将花费一个小时重写大量代码的情况,Bharathwaaj的评论是完全错误的,而这个答案是完美可行的。 - Shariq
@Shariq,问题在于on_thread_finished仍在子线程中运行,而不是在Manager中。你不能让Manager这样做,因为此时Manager正在做其他事情。要查看问题,请在调用MyThread()后使Test()挂起在死循环中。或者,定义一个本地变量并尝试在on_thread_finished()中使用它,例如number_of_threads_running -=1 - Be Brave Be Like Ukraine
1
如果管理器在tkinter GUI中,这将无法工作,您无法从工作线程调用GUI的函数。 - Jason

4

如果你想让主线程等待子线程执行完毕,最好使用某种同步机制。如果只是想在一个或多个线程完成执行时得到通知,那么Condition就足够了:

import threading

class MyThread(threading.Thread):
    def __init__(self, condition):
        threading.Thread.__init__(self)
        self.condition = condition

    def run(self):
        print "%s done" % threading.current_thread()
        with self.condition:
            self.condition.notify()


condition = threading.Condition()
condition.acquire()

thread = MyThread(condition)
thread.start()

condition.wait()

然而,使用队列(Queue)可能更好,因为它可以使处理多个工作线程变得更加容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接