Created
April 1, 2020 05:25
-
-
Save Xevion/c15bff14dcb70edd56da4e58b4df4607 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import sys | |
import time | |
import traceback | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import QFont | |
from PyQt5.QtWidgets import * | |
class WorkerSignals(QObject): | |
finished = pyqtSignal() | |
starting = pyqtSignal(int) | |
result = pyqtSignal(tuple) | |
class Worker(QRunnable): | |
def __init__(self, obj, *args, **kwargs): | |
super(Worker, self).__init__() | |
# Store constructor arguments (re-used for processing) | |
self.obj = obj | |
self.args = args | |
self.kwargs = kwargs | |
self.signals = WorkerSignals() | |
@pyqtSlot() | |
def run(self): | |
try: | |
wait = self.obj.getRefreshTime() | |
print(f'Thread {self.obj.id} sleeping for {wait} seconds.') | |
time.sleep(wait) | |
self.signals.starting.emit(self.obj.id) | |
result = self.obj.refresh(*self.args, **self.kwargs) | |
print(f'Thread {self.obj.id} finished.') | |
self.signals.result.emit(result) # Return the result of the processing | |
except: | |
traceback.print_exc() | |
finally: | |
self.signals.finished.emit() # Done | |
class Thread(object): | |
""" | |
Basic Rules for a Thread Object: | |
Cannot store the next timestamp on the object (it's a database object, I don't believe it's good practice | |
to be creating sessions over and over to simply read/write the access time. | |
ID and Active are allowed as booleans. | |
""" | |
i = -1 | |
def __init__(self): | |
self.id = Thread.nextID() | |
self.active = True | |
self.refreshes = 0 | |
def refresh(self) -> tuple: | |
# Represents my SQL Alchemy Model's refresh() function | |
self.refreshes += 1 | |
time.sleep(random.randint(2, 5)) | |
if random.random() <= max(0.1, 1.0 - ((self.refreshes + 5) / 10)): | |
self.active = False | |
return self.active, self.id | |
def getRefreshTime(self) -> float: | |
# Provides the next interval this thread should wait before refresh() should be called. | |
# Should NOT be used to determine whether the thread is still active or not. | |
return random.uniform(3, 10) | |
@staticmethod | |
def nextID(): | |
Thread.i += 1 | |
return Thread.i | |
def __repr__(self): | |
return f'Thread(id={self.id} active={self.active})' | |
class MainWindow(QMainWindow): | |
def __init__(self, *args, **kwargs): | |
super(MainWindow, self).__init__(*args, **kwargs) | |
# Widgets Setup | |
layout = QVBoxLayout() | |
self.list = QListWidget() | |
self.l = QLabel("Total Active: 0") | |
self.button = QPushButton("Refresh List") | |
self.button.pressed.connect(self.refreshList) | |
self.button.setDisabled(True) | |
layout.addWidget(self.l) | |
layout.addWidget(self.button) | |
layout.addWidget(self.list) | |
w = QWidget() | |
w.setLayout(layout) | |
self.setCentralWidget(w) | |
self.show() | |
self.atimer = QTimer() | |
self.atimer.setInterval(5_000) | |
self.atimer.timeout.connect(self.addThreads) | |
# Threading Setup | |
self.threadpool = QThreadPool() | |
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) | |
self.active = [] | |
self.threads = [] | |
# self.addToPool([Thread() for _ in range(random.randint(20, 45))]) | |
self.atimer.start() | |
def refreshList(self): | |
# This shouldn't be central to the program's usability. Everything should process normally in the background. | |
self.list.clear() | |
bold = QFont() | |
bold.setBold(True) | |
active = 0 | |
for thread in filter(lambda t : t.active, self.threads): | |
item = QListWidgetItem( | |
f'Thread {thread.id}/{thread.refreshes}') | |
if self.active[thread.id]: | |
active += 1 | |
item.setFont(bold) | |
self.list.addItem(item) | |
self.l.setText(f'Total Active: {active}/{len(list(filter(lambda t : t.active, self.threads)))}') | |
def refreshResult(self, result): | |
self.active[result[1]] = False | |
if result[0]: | |
print(f'Restarting Thread {result[1]}') | |
self.addToPool([self.threads[result[1]]], restart=True) # Add by ID, which would normally be a database GET | |
else: | |
print(f'Thread {result[1]} shutting down.') | |
self.refreshList() | |
def updateActivity(self, id): | |
print(f'Thread {id} is starting.') | |
self.active[id] = True | |
self.refreshList() | |
def addToPool(self, thread, restart=False): | |
if type(thread) is list: | |
for subthread in thread: | |
self.addToPool(subthread, restart=restart) | |
else: | |
if not restart: | |
self.active.append(False) | |
self.threads.append(thread) | |
print(f'Adding to pool - {thread.id}') | |
worker = Worker(thread) | |
worker.signals.result.connect(self.refreshResult) | |
worker.signals.starting.connect(self.updateActivity) | |
self.threadpool.start(worker) | |
self.refreshList() | |
@property | |
def activeThreads(self): | |
return list(filter(lambda t : self.active[t.id], self.threads)) | |
def addThreads(self): | |
print('Adding Threads...') | |
target = 25 + random.randint(-10, 8) | |
add = target - len(self.threads) | |
if add > 0: | |
print(f'Adding {add} thread{"s" if add > 1 else ""} to pool.') | |
self.addToPool([Thread() for _ in range(add)]) | |
app = QApplication([]) | |
window = MainWindow() | |
app.exec_() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment