Qt threading done right (thread pool for word counter)

This commit is contained in:
Veronica K. B. Olsen
2020-10-07 15:04:13 +02:00
parent b6a4568254
commit 0c1aaecd78
2 changed files with 46 additions and 38 deletions
+44 -37
View File
@@ -12,6 +12,7 @@
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch
Created: 2020-06-27 [0.10.0] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
This file is a part of novelWriter
Copyright 20182020, Veronica Berglyd Olsen
@@ -36,7 +37,8 @@ import logging
from time import time
from PyQt5.QtCore import (
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression, QPointF
Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
QPointF, QObject, QRunnable
)
from PyQt5.QtGui import (
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
@@ -135,14 +137,15 @@ class GuiDocEditor(QTextEdit):
activated=self._followTag
)
# Set Up Word Count Thread and Timer
# Set Up Word Counter
self.wcInterval = self.mainConf.wordCountTimer
self.wcTimer = QTimer()
self.wcTimer.setInterval(int(self.wcInterval*1000))
self.wcTimer.timeout.connect(self._runCounter)
self.wCounter = BackgroundWordCounter(self)
self.wCounter.finished.connect(self._updateCounts)
self.wCounter.setAutoDelete(False)
self.wCounter.signals.countsReady.connect(self._updateCounts)
self.initEditor()
@@ -912,21 +915,18 @@ class GuiDocEditor(QTextEdit):
"""Decide whether to run the word counter, or stop the timer due
to inactivity.
"""
sinceActive = time()-self.lastEdit
if sinceActive > 5*self.wcInterval:
logger.debug(
"Stopping word count timer: no activity last %.1f seconds" % sinceActive
)
self.wcTimer.stop()
elif self.wCounter.isRunning():
logger.verbose("Word counter thread is busy")
else:
logger.verbose("Starting word counter")
self.wCounter.start()
if self.wCounter.isRunning():
logger.verbose("Word counter is busy")
return
if time() - self.lastEdit < 5*self.wcInterval:
logger.verbose("Running word counter")
self.theParent.threadPool.start(self.wCounter)
return
@pyqtSlot()
def _updateCounts(self):
@pyqtSlot(int, int, int)
def _updateCounts(self, cCount, wCount, pCount):
"""Slot for the word counter's finished signal
"""
theItem = self.nwDocument.getCurrentItem()
@@ -935,19 +935,17 @@ class GuiDocEditor(QTextEdit):
logger.verbose("Updating word count")
self.charCount = self.wCounter.charCount
self.wordCount = self.wCounter.wordCount
self.paraCount = self.wCounter.paraCount
theItem.setCharCount(self.charCount)
theItem.setWordCount(self.wordCount)
theItem.setParaCount(self.paraCount)
self.charCount = cCount
self.wordCount = wCount
self.paraCount = pCount
theItem.setCharCount(cCount)
theItem.setWordCount(wCount)
theItem.setParaCount(pCount)
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount)
self.theParent.treeView.propagateCount(self.theHandle, wCount)
self.theParent.treeView.projectWordCount()
self.theParent.treeMeta.updateCounts(
self.theHandle, self.charCount, self.wordCount, self.paraCount
)
self._checkDocSize(self.charCount)
self.theParent.treeMeta.updateCounts(self.theHandle, cCount, wCount, pCount)
self._checkDocSize(self.qDocument.characterCount())
self.docFooter.updateCounts()
return
@@ -1521,33 +1519,42 @@ class GuiDocEditor(QTextEdit):
# END Class GuiDocEditor
# =============================================================================================== #
# The Off GUI Thread Word Counter
# Runs the word counter in the background for the DocEditor
# The Off-GUI Thread Word Counter
# A runnable for the word counter to be run in the thread pool off the main GUI thread.
# =============================================================================================== #
class BackgroundWordCounter(QThread):
class BackgroundWordCounter(QRunnable):
def __init__(self, docEditor):
QThread.__init__(self, docEditor)
QRunnable.__init__(self)
self.docEditor = docEditor
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
self.signals = BackgroundWordCounterSignals()
self._isRunning = False
return
def isRunning(self):
return self._isRunning
@pyqtSlot()
def run(self):
"""Overloaded run function for the word counter, forwarding the
call to the function that does the actual counting.
"""
self._isRunning = True
theText = self.docEditor.getText()
cC, wC, pC = countWords(theText)
self.charCount = cC
self.wordCount = wC
self.paraCount = pC
self.signals.countsReady.emit(cC, wC, pC)
self._isRunning = False
return
## END Class BackgroundWordCounter
class BackgroundWordCounterSignals(QObject):
countsReady = pyqtSignal(int, int, int)
# END Class BackgroundWordCounterSignals
# =============================================================================================== #
# The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
+2 -1
View File
@@ -32,7 +32,7 @@ import os
from datetime import datetime
from time import time
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtCore import Qt, QTimer, QThreadPool
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
from PyQt5.QtWidgets import (
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
@@ -60,6 +60,7 @@ class GuiMain(QMainWindow):
logger.debug("Initialising GUI ...")
self.setObjectName("GuiMain")
self.mainConf = nw.CONFIG
self.threadPool = QThreadPool()
# Some runtime info useful for debugging
logger.info("OS: %s" % self.mainConf.osType)