python - 如何在线程完成工作后停止线程并在按下按钮时重新启动线程?

标签 python python-3.x multithreading user-interface wxpython

我正在为游戏创建一个安装程序,到目前为止有两个按钮。一种是下载游戏,一种是在检测到可执行文件时启动游戏。我对这两个按钮进行了多线程处理,这样当我单击任一按钮时,我的 GUI 都不会卡住。问题是,如果我单击其中一个按钮,另一个按钮将无法工作,直到重新启动应用程序。我需要某种方法让线程在其进程完成后关闭,以便该线程打开以供其他按钮工作。

这是我到目前为止所拥有的:

# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *

# Define global variables
url = "{ENTER DROPBOX URL HERE}" # The url to the file we are downloading
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
ID_START = wx.NewId()# Button definitions
EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion

# Version Check
def VersionCheck():
    try:
        CurrentVersion = os.listdir("./RFMB6_WINDOWS/")[0] # Checks the version currently downloaded
        VersionCheck = requests.get('https://pastebin.com/raw/yc30uwAh') # Checks the newest version
        NewestVersion = VersionCheck.text # Converts VersionCheck to a string

        if CurrentVersion == NewestVersion:
            message = 'It looks like you have the newest version already.\n Are you sure you want to download?'
            wx.MessageBox(message=message, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)

        else:
            print('\n\nThere is an update available, would you like to install it?')
            pass
    except:
        print("It looks like you don't have RFMP installed yet. Let me fix that for you.")

# Downloads new file
def Download():
    urllib.request.urlretrieve(url, 'RFMP.zip')

# Extracts new file
def Extract():
    zip_ref = zipfile.ZipFile("RFMP.zip", 'r')
    zip_ref.extractall("RFMB6_WINDOWS")
    zip_ref.close()

# Deletes the .zip file but leave the folder
def Clean():
    os.remove("RFMP.zip")

class ProgressEvent(wx.PyCommandEvent):
    """Event to signal that a status or progress changed"""
    def __init__(self, etype, eid, status=None, progress=None):
        """Creates the event object"""
        wx.PyCommandEvent.__init__(self, etype, eid)
        self._status = status       # field to update label
        self._progress = progress   # field to update progress bar

    def GetValue(self):
        """Returns the value from the event.
        @return: the tuple of status and progress
        """
        return (self._status, self._progress)

# Thread class that executes processing
class DLThread(Thread):
    """Worker Thread Class."""
    def __init__(self, notify_window):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self._notify_window = notify_window
        self.start()

    # This is what runs on a separate thread when you click the download button
    def run(self):
        # This is the code executing in the new thread.
        self.sendEvent('Checking for old files...', 00)
        self.sendEvent('Checking for old files...', 100)
        time.sleep(.5)
        if os.path.exists("RFMB6_WINDOWS"):
            self.sendEvent('Removing old files...', 200)
            subprocess.check_call(('attrib -R ' + 'RFMB6_WINDOWS' + '\\* /S').split())
            shutil.rmtree('RFMB6_WINDOWS')
            time.sleep(.3)
            self.sendEvent('Removed old files.', 300)
        else:
            time.sleep(.3)
            self.sendEvent('No old files found.', 300)
            time.sleep(.3)
            pass
        self.sendEvent('Downloading Package...', 400)
        Download()
        self.sendEvent('Downloading complete.', 600)
        time.sleep(.3)
        self.sendEvent('Extracting...', 650)
        Extract()
        self.sendEvent('Extraction complete.', 900)
        time.sleep(.3)
        self.sendEvent('Cleaning up...', 950)
        Clean()
        time.sleep(.3)
        self.sendEvent('Cleaning complete.', 1000)
        time.sleep(.5)
        done = ("Installation the RFMP Private Alpha has been completed!")
        wx.MessageBox(message=done, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)
        self._notify_window.worker = None

    def sendEvent(self, status=None, progress=None):
        # Send event to main frame, first param (str) is for label, second (int) for the progress bar
        evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
        wx.PostEvent(self._notify_window, evt)

class StartAppThread(Thread):
    """Worker Thread Class."""
    def __init__(self, notify_window):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self._notify_window = notify_window
        # This starts the thread running on creation.
        self.start()

    # This is what runs on a separate thread when you click the download button
    def run(self):
        try:
            subprocess.run('RFMB6_WINDOWS/RFMB6_WINDOWS/RFMB6.exe')
        except:
            error = ("Failed to locate RFMB6.exe. Please don't move any game files after downloading.")
            wx.MessageBox(message=error, caption='RFMP GUIntaller | Error!',
            style=wx.OK | wx.ICON_ERROR)
        self._notify_window.worker = None

# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
    """Class MainFrame."""    

    def __init__(self, parent, id):
        """Create the MainFrame."""
        wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller', 
                          style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
                          ^ wx.MAXIMIZE_BOX)
        self.SetSize(400, 350)
        self.Centre()

        DLStart = wx.Button(self.bitmap1, ID_START, 'Download RFMP', size=(175,50), pos=(50,260))
        DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)
        AppStart = wx.Button(self.bitmap1, ID_START, 'Start RFMP', size=(175,50), pos=(50,160))
        AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)
        self.status = wx.StaticText(self.bitmap1, -1, '', pos=(10,215), style=wx.NO_BORDER)
        self.status.SetBackgroundColour((255,255,0)) # set text back color
        self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230),
                              style =  wx.GA_HORIZONTAL)

        # And indicate we don't have a worker thread yet
        self.worker = None
        self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function

    def OnButton_DLStart(self, event):
        # Trigger the worker thread unless it's already busy
        VersionCheck()
        if not self.worker:
            self.worker = DLThread(self)

    def OnButton_AppStart(self, event):
        if not self.worker:
            self.worker = StartAppThread(self)

    def OnResult(self, event):
        """Our handler for our custom progress event."""
        status, progress = event.GetValue()
        self.status.SetLabel(status)
        if progress:
            self.gauge.SetValue(progress)

class MainApp(wx.App):
    """Class Main App."""
    def OnInit(self):
        """Init Main App."""
        self.frame = MainFrame(None, -1)
        self.frame.Show(True)
        self.SetTopWindow(self.frame)
        return True

# Main Loop
if __name__ == '__main__':
    app = MainApp(0)
    app.MainLoop()

最佳答案

您的问题是由 self.worker 有一个值引起的。
您需要重置self.worker
下面我调整了你的代码来做到这一点,并在这样做时我将 notify_window 重命名为 parent,只是因为它使正在发生的事情更加明显并且符合 python 标准。我确信还有很多其他方法可以实现这一目标,在本例中,这只是实现它的一种简单方法。

import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *

class DLThread(Thread):
    """Worker Thread Class."""
    def __init__(self, parent):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self.parent = parent
        self.stop_download = 0
        self.setDaemon(1)
        self.start()

    def run(self):
        # This is the code executing in the new thread.
        '''
        This is what runs on a separate thread when you click the download button
        '''
        x = 0
        while self.stop_download == 0:
            time.sleep(0.5)
            x +=1
            if x > 20:
                self.stop_download = 1
            print ("Downloading App", x)
        print("Download finished")
        self.parent.worker = None

    def stop(self):
        self.stop_download = 1
        print ("Download Cancelled")

class StartAppThread(Thread):
    """Worker Thread Class."""
    def __init__(self, parent):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self.parent = parent
        self.stop_app_thread = 0
        self.setDaemon(1)
        self.start()

    def run(self):
        # This is the code executing in the new thread.
        '''
        This is what runs on a separate thread when you click the Start App button.
        '''
        x= 0
        while self.stop_app_thread == 0:
            print ("Game in progress",str(x))
            time.sleep(0.5)
            x +=1
        print ("Game finished")
        self.parent.worker = None

    def stop(self):
        self.stop_app_thread = 1

# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
    """Class MainFrame."""
    #Main Window
    def __init__(self, parent, id):
        """Create the MainFrame."""
        wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
                          style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
                          ^ wx.MAXIMIZE_BOX)
        self.SetSize(400, 350)
        #self.bitmap1 = wx.StaticBitmap(self)
        self.bitmap1 = wx.Panel(self)
        self.Centre()

        # Variables
        myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
        EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
        ID_START = wx.NewId()# Button definitions
        EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion

        # Download button
        DLStart = wx.Button(self.bitmap1, ID_START, 'Download', size=(175,50), pos=(50,260))
        DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)

        # App Start button
        AppStart = wx.Button(self.bitmap1, ID_START, 'Start App', size=(75,50), pos=(50,160))
        AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)

        # App Stop button
        AppStop = wx.Button(self.bitmap1, ID_START, 'Stop', size=(75,50), pos=(150,160))
        AppStop.Bind(wx.EVT_BUTTON, self.OnButton_AppStop)

        # Progress bar
        self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230), style =  wx.GA_HORIZONTAL)

        # And indicate we don't have a worker thread yet
        self.worker = None
        self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function

    def OnButton_DLStart(self, event):
        # Trigger the worker thread unless it's already busy
        if not self.worker:
            self.worker = DLThread(self)

    def OnButton_AppStart(self, event):
        if not self.worker:
            self.worker = StartAppThread(self)

    def OnButton_AppStop(self, event):
        if self.worker:
           self.worker.stop()
        print ("App Stop command")

    def OnResult(self, event):
        """Our handler for our custom progress event."""
        status, progress = event.GetValue()
        self.status.SetLabel(status)
        if progress:
            self.gauge.SetValue(progress)

class MainApp(wx.App):
    """Class Main App."""
    def OnInit(self):
        """Init Main App."""
        self.frame = MainFrame(None, -1)
        self.frame.Show(True)
        self.SetTopWindow(self.frame)
        return True

# Main Loop
if __name__ == '__main__':
    app = MainApp(0)
    app.MainLoop()

关于python - 如何在线程完成工作后停止线程并在按下按钮时重新启动线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52526022/

相关文章:

python - 安装基于 pyproject.toml 的项目所需的错误 : Could not build wheels for pymssql,

python - (DJANGO-ALLAUTH) 选择一个有效的选项。谷歌不是可用的选择之一

Python 兼容性 : Catching exceptions

python-3.x - python 3的Virtualenvwrapper替代品

python - Python C API 中的静态变量

python - 使用函数调用填充 pandas 数据框中的列

java - "delegation"如何帮助线程安全类?

java - 如何配置 spring 来执行重叠的 fixedRate 任务?

python - 计算列表内重复列表的数量

c++ - C 可重入函数