【问题标题】:decorator for extra thread额外线程的装饰器
【发布时间】:2016-09-09 07:02:33
【问题描述】:

我正在尝试在 gui 后面自动执行任务。我的 gui 有多个按钮可以启动某些功能,一个按钮可以停止当前正在运行的任何功能。当一个功能正在运行时,除“取消”按钮外的所有按钮都显示为灰色。我需要额外的线程,以便我的 gui 在冗长的函数运行时仍然响应。

现在我想实现一个装饰器来装饰这些功能。这个装饰器应该在一个单独的线程中运行这个函数。当用户按下取消按钮时,这个额外线程中的函数应该接收到某种停止信号,装饰函数可以使用该信号来停止其当前任务并退出。

我知道可以为每个函数实现一个 threading.Thread 类型的类并将当前函数插入为“def run(self):”,但这似乎不是一个优雅的解决方案。

有办法吗?对我来说,这似乎是一个常见问题,但除了将函数编写为类并将它们作为单独的线程运行之外,我还没有通过 Google 找到任何解决方案。

编辑 1:

让我添加一些例子。现在我的函数看起来像这样:

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 类不是一个优雅的解决方案?
  • @AlexHall:我不想杀死它,我希望函数处理停止信号并自行退出。
  • @julivico:因为我必须编写 5 个类而不是 5 个函数和一个装饰器。除了里面的 run-function 之外,这些类是相同的。我知道我可以从其中一个继承并更改每个的运行功能。对于这个看似简单的任务来说似乎还是太复杂了。
  • 它并不复杂,实际上它更简单,不仅如此,它更易于阅读和维护。你陷入了代码越少越好的陷阱。

标签: python multithreading decorator


【解决方案1】:

装饰器需要创建一种与线程通信的方法。在此示例中,每个应作为线程运行的函数必须首先具有 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()

【讨论】:

  • 我不是这个意思。我不想要多个任务并等待所有任务。我想要 1 个任务,根本不等待,并且可以选择向在此任务中运行的函数发送停止请求。
  • @Stefan 你不必等待,你可以打电话给print_somedata() 继续。他只是向您展示了如果您想要也可以访问线程,这通常很重要(例如,当您确实需要等待任务结束,或者至少检查它们是否已经完成时,这通常是)。停止线程的选项不能添加到装饰器中,事实上,如果可以的话,这不是一个好主意,因为这是一个单独的问题。但是你说你希望函数优雅地停止,所以你每次都需要手动编写代码,因为它并不总是一样的。
  • @salomonderossi:看起来我应该能够自己理解其余部分,但我不明白。如何发送停止命令?如果我将“if stopFlag: return”放在所有打印和睡眠语句之间,我如何从主函数编辑它并在线程打印“finished print_somedata”之前停止线程?
  • @salomonderossi:所以 queue.put('stop') 以某种方式清空了队列,这反过来又使 queue.get(False) 返回了 Queue.Empty-Error。我仍然不完全理解它为什么会这样工作(特别是为什么 queue.put('stop') 命令会清空队列),但我可以自己研究这部分。非常感谢您的帮助!
  • 我想我明白了:如果放入队列的项目不是 Queue.Queue 类型,则该项目不会立即可用(如 Queue.get() 下的here 解释)因此引发了上述错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 2015-09-05
  • 2021-04-11
  • 2012-04-27
相关资源
最近更新 更多