装饰器用于额外的线程

3
我正在尝试自动化GUI后面的任务。我的GUI有多个按钮,这些按钮应该启动特定的函数,并且有一个按钮可以停止当前正在运行的任何函数。当一个函数正在运行时,除“取消”按钮外的所有按钮都会变灰。我需要额外的线程,以便在漫长的功能运行时,我的GUI仍然能够响应。
现在,我想实现一个装饰器来装饰这些函数。此装饰器应在单独的线程中运行函数。当用户按下取消按钮时,此额外线程中的函数应接收某种停止信号,装饰函数可以使用该信号停止其当前任务并退出。
我知道可以为每个函数实现类型为threading.Thread的类,并将当前函数插入为“def run(self):”,但这似乎不是一种优雅的解决方案。
有办法吗?对我来说,这似乎是一个常见的问题,但我没有通过Google找到任何解决方案,除了编写函数作为类并将其作为单独的线程运行。
def function1:
    function_code

但是,如果我创建类,它将会是这样的:
class class1(threading.Thread):
    stopper = None
    def __init__(self):
        init_code
    def run(self):
        function_code

 def function1:
    t = class1()
    t.stopper = some_stop_condition
    t.run()

第二段代码要求为每个按钮编写一个类和函数,代码量更大,看起来更加复杂,我希望这并非必需。我是否理解错误或者做错了什么?
编辑2:
在salomonderossi的优秀示例之后,我的新代码:
def run_async(func):
    @functools.wraps(func)
    def async_func(*args, **kwargs):
        queue = Queue.Queue()
        t = threading.Thread(target=func, args=(queue,) + args, kwargs=kwargs)
        t.start()
        return queue, t

    return async_func


# @layout_decorators.runInSeparateThread()
@run_async
def test2(myQueue):
    print "executing test2"
    import time
    for k in range(6):
        print k
        try:
            myQueue.get(False)
        except Queue.Empty:
            print "cancelled"
            return
        time.sleep(1)


def test22():
    print "executing test22"
    global globalQueue
    globalQueue, t = test2()


if __name__ == "__main__":
    import time
    print "\n\n"
    test22()
    time.sleep(2)
    globalQueue.put("stop")

但它在第一次机会就停止了线程。即使我删除了最后一行代码(我认为是停止线程的),我的输出结果仍然是:

executing test22
executing test2
0
cancelled

为什么threading.Thread类不是一个优雅的解决方案? - qvpham
@AlexHall:我不想杀掉它,我希望函数能够处理停止信号并自行退出。 - Stefan
因为我需要编写5个类而不是5个函数和一个装饰器。这些类将除了内部的run函数之外都是相同的。我知道我可以从其中一个继承并为每个更改run函数。但对于这个看似简单的任务来说,还是太复杂了。 - Stefan
事实上,它并不更加复杂,相反,它更简单,而且不仅如此,它更易于阅读和维护。你正在陷入代码越少越好的误区。 - Rolf of Saxony
1个回答

3
装饰器需要创建一种与线程通信的方式。在这个例子中,每个应该作为线程运行的函数都必须在第一个位置有queue参数。这个queue用于与线程通信。在这个例子中,可以将任何东西放入队列来停止线程,因为函数只检查是否可以从队列中取出值。
使用queue.get(False)尝试从队列中获取元素(无需等待)。如果没有元素(队列为空),则会引发异常。否则,队列中有东西并告诉线程退出。
要告诉线程退出,必须在线程队列中放置一些东西。这是通过queue.put("stop")完成的。在这种情况下,参数无关紧要。
这意味着,您必须定期检查队列中是否有东西,并对其做出反应(在本例中只需停止处理)。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from threading import Thread
from functools import wraps
from time import sleep

try:
    import queue as Queue
except ImportError:
    import Queue as Queue


def run_async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        queue = Queue.Queue()
        t = Thread(target=func, args=(queue,) + args, kwargs=kwargs)
        t.start()
        return queue, t

    return async_func

@run_async
def do_something_else(queue):
    while True:
        sleep(1)
        print("doing something else")
        # check if something in the queue and return if so
        try:
            queue.get(False)
        except Queue.Empty:
            pass
        else:
            print("Told to quit")
            return


@run_async
def print_somedata(queue):
    print('starting print_somedata')
    sleep(2)
    try:
        queue.get(False)
    except Queue.Empty:
        pass
    else:
        print("Told to quit")
        return

    print('print_somedata: 2 sec passed')
    sleep(2)
    try:
        queue.get(False)
    except Queue.Empty:
        pass
    else:
        print("Told to quit")
        return

    print('print_somedata: another 2 sec passed')
    sleep(2)
    try:
        queue.get(False)
    except Queue.Empty:
        pass
    else:
        print("Told to quit")
        return

    print('finished print_somedata')


def test():
    threads = list()

    # at this moment the thread is created and starts immediately
    threads.append(print_somedata())
    print('back in main')

    # at this moment the thread is created and starts immediately
    threads.append(print_somedata())
    print('back in main')

    # at this moment the hread is created and starts immediately
    threads.append(do_something_else())
    print('back in main')

    # at this moment the hread is created and starts immediately
    threads.append(do_something_else())
    print('back in main')

    print("Wait a bit in the main")
    sleep(1)  # uncomment the wait here to stop the threads very fast ;)

    # you don't have to wait explicitly, as the threads are already
    # running. This is just an example to show how to interact with
    # the threads
    for queue, t in threads:
        print("Tell thread to stop: %s", t)
        queue.put('stop')
        #t.join()

if __name__ == '__main__':
    test()

这不是我的意思。我不想要多个任务并等待它们全部完成。我只想要一个任务,完全不等待,并且有一个选项可以发送停止请求到正在运行此任务的函数。 - Stefan
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Alex Hall
@salomonderossi: 看起来我自己应该能理解其余部分,但我不明白。我如何发送停止命令?如果我在所有的打印和睡眠语句之间放置“if stopFlag:return”,那么我如何从主函数中编辑它并在它们打印“完成print_somedata”之前停止线程? - Stefan
@salomonderossi:所以queue.put('stop')会以某种方式清空队列,从而使queue.get(False)返回一个Queue.Empty错误。我仍然不完全理解为什么会这样工作(特别是为什么queue.put('stop')命令会清空队列),但我可以自己研究这部分内容。非常感谢您的帮助! - Stefan
我想我明白了:如果放入队列的项目不是Queue.Queue类型,则该项目不会立即可用(如此处在Queue.get()下所述),因此会引发上述错误。 - Stefan
显示剩余3条评论

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