Skip to main content
 首页 » 编程设计

python之多次调用时如何停止长时间运行的函数

2025年05月04日11fff_TT

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

因此,我的问题是如何在后续调用中中断 _dataCruncher 的正常执行,以便让 GUI 可以自由地做其他事情,并且在它不在时不浪费处理时间需要吗?我意识到我可能需要使用一个线程来运行 _dataCruncher 和某种 Queue 来获得适当的 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() 

请您参考如下方法:

所以,找到这个问题的答案比我想象的要复杂。正如@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')