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

2024-04-23 15:40:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个安装程序,我正在为一个游戏,现在有两个按钮。一个下载游戏,一个启动游戏,如果它检测到可执行文件。我对这两个按钮都进行了多线程处理,这样当我单击其中一个按钮时,我的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()

Tags: thetoselfeventtimedefstatusnotify
1条回答
网友
1楼 · 发布于 2024-04-23 15:40:02

您的问题是由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()

相关问题 更多 >