Python 当一个长时间运行的函数被多次调用时,我如何停止它?

Python 当一个长时间运行的函数被多次调用时,我如何停止它?,python,multithreading,python-3.x,pyqt,pyqt4,Python,Multithreading,Python 3.x,Pyqt,Pyqt4,下面我有一个示例程序。按下按钮时,需要一秒钟才能计算出要显示的值。如果用户连续快速按下按钮,他们会等待很长时间才能看到最后一个答案,这是他们唯一关心的答案。在代码中,您可以看到\u dataCruncher函数需要知道self.\u count,但是self.\u count不依赖于\u dataCruncher的输出 因此,我的问题是,如何在后续调用中中断\u dataCruncher的正常执行,以便让GUI自由地做其他事情,并且在不需要时不浪费处理时间?我意识到我可能需要使用一个线程来运行\

下面我有一个示例程序。按下按钮时,需要一秒钟才能计算出要显示的值。如果用户连续快速按下按钮,他们会等待很长时间才能看到最后一个答案,这是他们唯一关心的答案。在代码中,您可以看到
\u dataCruncher
函数需要知道
self.\u count
,但是
self.\u count
不依赖于
\u dataCruncher
的输出

因此,我的问题是,如何在后续调用中中断
\u dataCruncher
的正常执行,以便让GUI自由地做其他事情,并且在不需要时不浪费处理时间?我意识到我可能需要使用一个线程来运行
\u dataCruncher
,并使用某种队列来显示相应的
val
,但我不知道如何将这些放在一起

from PyQt4 import QtGui, QtCore
import sys
import time
import random
import random

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        self.app = QtGui.QApplication(sys.argv)
        super(MainWindow, self).__init__()
        self.count = 0
        self.initUI()

    def initUI(self):
        # Layouts
        central = QtGui.QWidget()
        layout = QtGui.QVBoxLayout()

        self.button = QtGui.QPushButton('Press Me')
        self.text = QtGui.QLabel('?')

        layout.addWidget(self.button)
        layout.addWidget(self.text)

        central.setLayout(layout)
        self.setCentralWidget(central)

        self.button.clicked.connect(self._buttonClicked)

    def _dataCruncher(self, val):
        time.sleep(1) # takes a long time to process data using val
        return val * random.randint(1,10)

    def _buttonClicked(self):
        self.count += 1
        val = self._dataCruncher(self.count)
        self.text.setText('Value {}'.format(val))

    def startup(self):
        self.show()
        result = self.app.exec_()
        sys.exit(result)

if __name__ == '__main__':
    random.seed()
    myWindow = MainWindow()
    myWindow.startup()

您可以在按下按钮后禁用该按钮,直到准备好回答为止,方法是: setEnabled(错误)
然后在提供结果之前将其重置。

您可以在按下按钮后禁用该按钮,直到准备好回答为止,方法是: setEnabled(错误)
然后在提供结果之前重置它。

因此,找到这个问题的答案比我想象的要复杂。正如@MTset在其中一条评论中提到的,python不提供任何取消线程执行的方法。所以,我所做的是创建一个“threadHandler”类来处理线程。它跟踪创建的最后一个线程,并提供从最后一个线程的执行中获取结果的方法

我发布了原始帖子中测试代码的修改版本,以及完整的threadHandler代码,以防有人使用它

这里是文件1

# tester.py, run this file

from PyQt4 import QtGui, QtCore
import random, sys, time
from threadHandler import MyThreadHandler

class MyModel(object):
    def dataCruncher(self, val):
        delay = random.randint(1,5)
        print('{} sleeping for {}'.format(val, delay))
        time.sleep(delay) # takes a long time to process data using val
        print('{} done sleeping'.format(val))
        return val

class MainWindow(QtGui.QMainWindow):
    def __init__(self, threadHandler):
        self.app = QtGui.QApplication(sys.argv)
        super(MainWindow, self).__init__()
        self.count = 0
        self.initUI()
        self.button_clicked_events = Event()
        self.threadHandler = threadHandler

    def initUI(self):
        # Layouts
        central = QtGui.QWidget()
        layout = QtGui.QVBoxLayout()

        self.button = QtGui.QPushButton('Press Me')
        self.text = QtGui.QLabel('?')

        layout.addWidget(self.button)
        layout.addWidget(self.text)

        central.setLayout(layout)
        self.setCentralWidget(central)

        self.button.clicked.connect(self._buttonClicked)

    def _buttonClicked(self):
        self.count += 1
        self.button_clicked_events(self.count)

    def setLabel(self, val):
        self.text.setText(str(val))

    def startup(self):
        self.show()
        result = self.app.exec_()
        return result


class Event(list):
    """Event subscription.

    A list of callable objects. Calling an instance of this will cause a
    call to each item in the list in ascending order by index.

    Example Usage:
    >>> def f(x):
    ...     print 'f(%s)' % x
    >>> def g(x):
    ...     print 'g(%s)' % x
    >>> e = Event()
    >>> e()
    >>> e.append(f)
    >>> e(123)
    f(123)
    >>> e.remove(f)
    >>> e()
    >>> e += (f, g)
    >>> e(10)
    f(10)
    g(10)
    >>> del e[0]
    >>> e(2)
    g(2)

    """
    def __init__(self):
        self.output = {}

    def __call__(self, *args, **kwargs):
        for f,key in self:
            output = f(*args, **kwargs)
            self.output[key] = output
        return self.output
    def __repr__(self):
        return "Event({})".format(list.__repr__(self))

if __name__ == '__main__':
    def checker(handler, window):
        if handler.isLastDone():
            val = handler.getLastResult()
            window.setLabel(val)
        else:
            window.setLabel('calculating...')

    random.seed()
    model = MyModel()
    threadHandler = MyThreadHandler()
    myWindow = MainWindow(threadHandler)

    threadHandler.createTimer(1, checker, threadHandler, myWindow)

    def getData(count):
        threadHandler.createOneShot(model.dataCruncher, count)

    myWindow.button_clicked_events.append((getData, 'dt'))

    result = myWindow.startup()
    print('ending')
    threadHandler.end()
    print('ended')
    sys.exit(result)
文件2如下

#threadHandler.py, save this file in the same folder as tester.py

import threading, time

class MyThreadHandler(object):
    def __init__(self):
        self.oneShots = []
        self.timers = []
        self.oldOneShots = []
        self.latest = None
        self.cleaning = False

        self._startCleaner()

    def _startCleaner(self):
        print('-'*20+'Starting cleaner'+'-'*20)
        self.cleaner = self.createTimer(1, self._cleanupThreads)

    def _stopCleaner(self):
        print('-'*20+'Stopping cleaner'+'-'*20)
        self.cleaner.stop()

    def getNumThreads(self):
        return len(self.oneShots)

    def getNumOldThreads(self):
        return len(self.oldOneShots)

    def end(self):
        for i,timer in enumerate(self.timers):
            timer.stop()
            self.timers.pop(i)

    def createTimer(self, interval, func, *args, **kwargs):
        timer = myTimer(interval, func, args, kwargs)
        self.timers.append(timer)
        return timer

    def createOneShot(self, func, *args, **kwargs):
        oneshot = myOneShot(func, args, kwargs)
        self.oneShots.append(oneshot)
        self.latest = oneshot

    def isLastDone(self):
        if not self.latest is None:
            return not self.latest.running()
        else:
            return None

    def getLastResult(self):
        if self.latest is None:
            raise ValueError('There have not been any oneshots created.')
        while self.latest.running():
            pass
        result = self.latest.getResult()
        if len(self.oneShots) > 0:
            self.oldOneShots.append(myOneShot(self._cleanAll, (self.oneShots,)))
        self.oneShots = []
        return result

    def _cleanAll(self, toClean):
        # loop through toClean and pop up anything that's done. this DOES lock
        while len(toClean) > 0:
            toClean = self._cleanup(toClean)

    def _cleanup(self, toCleanup):
        while not self.cleaning:
            self.cleaning = True
            for i, thread in enumerate(toCleanup):
                if not thread.running():
                    toCleanup.pop(i)
        self.cleaning = False
        return toCleanup

    def _cleanupThreads(self):
        # check each of these lists and pop out any threads that are done. This
        # does not lock. This function should really only be called by the
        # cleaner, which is set up in __init__
        self.oneShots = self._cleanup(self.oneShots)
        self.timers = self._cleanup(self.timers)
        self.oldOneShots = self._cleanup(self.oldOneShots)

class myTimer(object):
    def __init__(self, delay, func, args=tuple(), kwargs={}):
        self.delay  = delay
        self.func   = func
        self.loop   = True
        self.args   = args
        self.kwargs = kwargs
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()
        self.output = None

    def run(self):
        while self.loop:
            self.output = self.func(*self.args, **self.kwargs)
            if self.delay > 0.1:
                count = 0
                while count <= self.delay:
                    count += 0.1
                    time.sleep(0.1)
            else:
                time.sleep(self.delay)

    def stop(self):
        self.loop = False

    def running(self):
        return self.loop

    def getResult(self):
        return self.output

class myOneShot(object):
    def __init__(self, func, args=tuple(), kwargs={}):
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()
        self.output = None

    def run(self):
        self.output = self.func(*self.args, **self.kwargs)

    def running(self):
        return self.thread.is_alive()

    def getResult(self):
        return self.output

if __name__ == '__main__':
    import random
    random.seed()

    def longFunc(num):
        delay = random.randint(5,8)
        if num in (3, 6):
            delay = 2
        print('-'*30+'func {} has sleep {}'.format(num, delay))
        time.sleep(delay)
        print('-'*30+'func {} is done'.format(num))
        return num

    def checker(handler):
        if handler.isLastDone():
            return handler.getLastResult()
        else:
            return None

    myHandler = MyThreadHandler()

    # The 'checker' function simulates something in my program that uses the
    # data generated by the 'longFunc'. It waits until there are no more threads
    # in the threadHandler, as that would indicate that the user is done
    # switching back-and-forth between different values
    checkTimer = myHandler.createTimer(1, checker, myHandler)

    # create 10 one-shot threads that take a 'long' time. The delay is to keep
    # them in order, as this loop is meant to simulate a user switching between
    # items using a keyboard or mouse, which I imagine they couldn't do any
    # faster than every 1/10th of a second
    start = time.time()
    for i in range(4):
        myHandler.createOneShot(longFunc, i)
        time.sleep(0.1)

    # wait until there are no more threads executing
    last = myHandler.getLastResult()

    print('result from last = {}'.format(last))

    for i in range(4, 7):
        myHandler.createOneShot(longFunc, i)
        time.sleep(0.1)

    last = myHandler.getLastResult()
    print('result from last = {}'.format(last))

    while myHandler.getNumOldThreads() >0 or myHandler.getNumThreads() > 0:
        pass

    myHandler.end()
    print('done ending')
#threadHandler.py,将此文件保存在与tester.py相同的文件夹中
导入线程,时间
类MyThreadHandler(对象):
定义初始化(自):
self.oneShots=[]
self.timers=[]
self.oldOneShots=[]
self.latest=无
自清洁=错误
self._startCleaner()
def_startCleaner(自清洁):
打印('-'*20+'启动清洁剂'+'-'*20)
self.cleaner=self.createTimer(1,self.\u cleanupThreads)
def_停车清洁剂(自动):
打印('-'*20+'Stopping cleaner'+'-'*20)
self.cleaner.stop()
def getNumThreads(自):
回程镜头(自拍照)
def getNumOldThreads(自):
回程镜头(自拍照)
def端(自身):
对于i,枚举中的计时器(self.timers):
计时器停止()
self.timers.pop(一)
def createTimer(self、interval、func、*args、**kwargs):
计时器=我的计时器(间隔、函数、参数、kwargs)
self.timers.append(计时器)
返回计时器
def createOneShot(self、func、*args、**kwargs):
oneshot=myOneShot(func、args、kwargs)
self.oneshot.append(oneshot)
self.latest=oneshot
def isLastDone(自我):
如果不是self.latest,则为None:
返回not self.latest.running()
其他:
一无所获
def getLastResult(自):
如果self.latest为“无”:
raise VALUETERROR('尚未创建任何oneshots')
当self.latest.running()时:
通过
结果=self.latest.getResult()
如果len(self.oneShots)>0:
self.oldOneShots.append(myOneShot(self.\u cleanAll,(self.oneShots,))
self.oneShots=[]
返回结果
def_清洁所有(自清洁、toClean):
#循环清理并弹出任何已完成的操作。这个锁不上
当len(toClean)>0时:
toClean=自清洁(toClean)
def_清理(自清理、toCleanup):
虽然不是自动清洁:
self.cleaning=True
对于i,枚举中的线程(toCleanup):
如果不是线程。正在运行():
toCleanup.pop(一)
自清洁=错误
返回清理
def_cleanupThreads(self):
#检查每个列表并弹出所有已完成的线程。这
#锁不上。此函数实际上只应由
#cleaner,它是在_init中设置的__
self.oneShots=self.\u清理(self.oneShots)
self.timers=self.\u清理(self.timers)
self.oldOneShots=self.\u清理(self.oldOneShots)
类myTimer(对象):
def uuu init uuuu(self,delay,func,args=tuple(),kwargs={}):
self.delay=延迟
self.func=func
self.loop=True
self.args=args
self.kwargs=kwargs
self.thread=threading.thread(target=self.run,daemon=True)
self.thread.start()
self.output=None
def运行(自):
而self.loop:
self.output=self.func(*self.args,**self.kwargs)
如果自延迟>0.1:
计数=0

虽然count如此,但找到这个问题的答案比我想象的要复杂得多。正如@MTset在其中一条评论中提到的,python不提供任何取消线程执行的方法。所以,我所做的是创建一个“threadHandler”类来处理线程。它跟踪创建的最后一个线程,并提供从最后一个线程的执行中获取结果的方法

我发布了原始帖子中测试代码的修改版本,以及完整的threadHandler代码,以防有人使用它

这里是文件1

# tester.py, run this file

from PyQt4 import QtGui, QtCore
import random, sys, time
from threadHandler import MyThreadHandler

class MyModel(object):
    def dataCruncher(self, val):
        delay = random.randint(1,5)
        print('{} sleeping for {}'.format(val, delay))
        time.sleep(delay) # takes a long time to process data using val
        print('{} done sleeping'.format(val))
        return val

class MainWindow(QtGui.QMainWindow):
    def __init__(self, threadHandler):
        self.app = QtGui.QApplication(sys.argv)
        super(MainWindow, self).__init__()
        self.count = 0
        self.initUI()
        self.button_clicked_events = Event()
        self.threadHandler = threadHandler

    def initUI(self):
        # Layouts
        central = QtGui.QWidget()
        layout = QtGui.QVBoxLayout()

        self.button = QtGui.QPushButton('Press Me')
        self.text = QtGui.QLabel('?')

        layout.addWidget(self.button)
        layout.addWidget(self.text)

        central.setLayout(layout)
        self.setCentralWidget(central)

        self.button.clicked.connect(self._buttonClicked)

    def _buttonClicked(self):
        self.count += 1
        self.button_clicked_events(self.count)

    def setLabel(self, val):
        self.text.setText(str(val))

    def startup(self):
        self.show()
        result = self.app.exec_()
        return result


class Event(list):
    """Event subscription.

    A list of callable objects. Calling an instance of this will cause a
    call to each item in the list in ascending order by index.

    Example Usage:
    >>> def f(x):
    ...     print 'f(%s)' % x
    >>> def g(x):
    ...     print 'g(%s)' % x
    >>> e = Event()
    >>> e()
    >>> e.append(f)
    >>> e(123)
    f(123)
    >>> e.remove(f)
    >>> e()
    >>> e += (f, g)
    >>> e(10)
    f(10)
    g(10)
    >>> del e[0]
    >>> e(2)
    g(2)

    """
    def __init__(self):
        self.output = {}

    def __call__(self, *args, **kwargs):
        for f,key in self:
            output = f(*args, **kwargs)
            self.output[key] = output
        return self.output
    def __repr__(self):
        return "Event({})".format(list.__repr__(self))

if __name__ == '__main__':
    def checker(handler, window):
        if handler.isLastDone():
            val = handler.getLastResult()
            window.setLabel(val)
        else:
            window.setLabel('calculating...')

    random.seed()
    model = MyModel()
    threadHandler = MyThreadHandler()
    myWindow = MainWindow(threadHandler)

    threadHandler.createTimer(1, checker, threadHandler, myWindow)

    def getData(count):
        threadHandler.createOneShot(model.dataCruncher, count)

    myWindow.button_clicked_events.append((getData, 'dt'))

    result = myWindow.startup()
    print('ending')
    threadHandler.end()
    print('ended')
    sys.exit(result)
文件2如下

#threadHandler.py, save this file in the same folder as tester.py

import threading, time

class MyThreadHandler(object):
    def __init__(self):
        self.oneShots = []
        self.timers = []
        self.oldOneShots = []
        self.latest = None
        self.cleaning = False

        self._startCleaner()

    def _startCleaner(self):
        print('-'*20+'Starting cleaner'+'-'*20)
        self.cleaner = self.createTimer(1, self._cleanupThreads)

    def _stopCleaner(self):
        print('-'*20+'Stopping cleaner'+'-'*20)
        self.cleaner.stop()

    def getNumThreads(self):
        return len(self.oneShots)

    def getNumOldThreads(self):
        return len(self.oldOneShots)

    def end(self):
        for i,timer in enumerate(self.timers):
            timer.stop()
            self.timers.pop(i)

    def createTimer(self, interval, func, *args, **kwargs):
        timer = myTimer(interval, func, args, kwargs)
        self.timers.append(timer)
        return timer

    def createOneShot(self, func, *args, **kwargs):
        oneshot = myOneShot(func, args, kwargs)
        self.oneShots.append(oneshot)
        self.latest = oneshot

    def isLastDone(self):
        if not self.latest is None:
            return not self.latest.running()
        else:
            return None

    def getLastResult(self):
        if self.latest is None:
            raise ValueError('There have not been any oneshots created.')
        while self.latest.running():
            pass
        result = self.latest.getResult()
        if len(self.oneShots) > 0:
            self.oldOneShots.append(myOneShot(self._cleanAll, (self.oneShots,)))
        self.oneShots = []
        return result

    def _cleanAll(self, toClean):
        # loop through toClean and pop up anything that's done. this DOES lock
        while len(toClean) > 0:
            toClean = self._cleanup(toClean)

    def _cleanup(self, toCleanup):
        while not self.cleaning:
            self.cleaning = True
            for i, thread in enumerate(toCleanup):
                if not thread.running():
                    toCleanup.pop(i)
        self.cleaning = False
        return toCleanup

    def _cleanupThreads(self):
        # check each of these lists and pop out any threads that are done. This
        # does not lock. This function should really only be called by the
        # cleaner, which is set up in __init__
        self.oneShots = self._cleanup(self.oneShots)
        self.timers = self._cleanup(self.timers)
        self.oldOneShots = self._cleanup(self.oldOneShots)

class myTimer(object):
    def __init__(self, delay, func, args=tuple(), kwargs={}):
        self.delay  = delay
        self.func   = func
        self.loop   = True
        self.args   = args
        self.kwargs = kwargs
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()
        self.output = None

    def run(self):
        while self.loop:
            self.output = self.func(*self.args, **self.kwargs)
            if self.delay > 0.1:
                count = 0
                while count <= self.delay:
                    count += 0.1
                    time.sleep(0.1)
            else:
                time.sleep(self.delay)

    def stop(self):
        self.loop = False

    def running(self):
        return self.loop

    def getResult(self):
        return self.output

class myOneShot(object):
    def __init__(self, func, args=tuple(), kwargs={}):
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()
        self.output = None

    def run(self):
        self.output = self.func(*self.args, **self.kwargs)

    def running(self):
        return self.thread.is_alive()

    def getResult(self):
        return self.output

if __name__ == '__main__':
    import random
    random.seed()

    def longFunc(num):
        delay = random.randint(5,8)
        if num in (3, 6):
            delay = 2
        print('-'*30+'func {} has sleep {}'.format(num, delay))
        time.sleep(delay)
        print('-'*30+'func {} is done'.format(num))
        return num

    def checker(handler):
        if handler.isLastDone():
            return handler.getLastResult()
        else:
            return None

    myHandler = MyThreadHandler()

    # The 'checker' function simulates something in my program that uses the
    # data generated by the 'longFunc'. It waits until there are no more threads
    # in the threadHandler, as that would indicate that the user is done
    # switching back-and-forth between different values
    checkTimer = myHandler.createTimer(1, checker, myHandler)

    # create 10 one-shot threads that take a 'long' time. The delay is to keep
    # them in order, as this loop is meant to simulate a user switching between
    # items using a keyboard or mouse, which I imagine they couldn't do any
    # faster than every 1/10th of a second
    start = time.time()
    for i in range(4):
        myHandler.createOneShot(longFunc, i)
        time.sleep(0.1)

    # wait until there are no more threads executing
    last = myHandler.getLastResult()

    print('result from last = {}'.format(last))

    for i in range(4, 7):
        myHandler.createOneShot(longFunc, i)
        time.sleep(0.1)

    last = myHandler.getLastResult()
    print('result from last = {}'.format(last))

    while myHandler.getNumOldThreads() >0 or myHandler.getNumThreads() > 0:
        pass

    myHandler.end()
    print('done ending')
#threadHandler.py,将此文件保存在与tester.py相同的文件夹中
导入线程,时间
类MyThreadHandler(对象):
定义初始化(自):
self.oneShots=[]
self.timers=[]
self.oldOneShots=[]
self.latest=无
自清洁=错误
self._startCleaner()
def_startCleaner(自清洁):
打印('-'*20+启动清洁剂'+