1# This file is part of the qpageview package. 2# 3# Copyright (c) 2019 - 2019 by Wilbert Berendsen 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18# See http://www.gnu.org/licenses/ for more information. 19 20""" 21Run jobs in the background using QThread. 22""" 23 24 25from PyQt5.QtCore import QThread 26 27# as soon as a Job start()s, it is saved here, to prevent it being 28# destroyed while running 29_runningJobs = set() 30_pendingJobs = [] 31maxjobs = 12 32 33 34class Job(QThread): 35 """A simple wrapper around QThread. 36 37 Before calling start() you should put the work function in the work 38 attribute, and an optional finalize function (which will be called with the 39 result) in the finalize attribute. 40 41 Or alternatively, inherit from this class and implement work() and finish() 42 yourself. The result of the work function is stored in the result attribute. 43 44 """ 45 finalize = None 46 running = False 47 done = False 48 result = None 49 50 def __init__(self, parent=None): 51 """Init ourselves; the parent can be a QObject which will be our parent.""" 52 super().__init__(parent) 53 self.finished.connect(self._slotFinished) 54 55 def start(self): 56 self.result = None 57 self.running = True # this is more robust than isRunning() 58 self.done = False 59 if len(_runningJobs) < maxjobs: 60 _runningJobs.add(self) 61 super().start() 62 else: 63 _pendingJobs.append(self) 64 65 def run(self): 66 """Call the work function in the background thread.""" 67 self.result = self.work() 68 69 def work(self): 70 """Implement this to get the work done. 71 72 If you have long tasks you can Qt's isInterruptionRequested() 73 functionality. 74 75 Instead of implementing this method, you can put the work function in 76 the work instance attribute. 77 78 """ 79 pass 80 81 def finish(self): 82 """This slot is called in the main thread when the work is done. 83 84 The default implementation calls the finalize function with the result. 85 86 """ 87 if self.finalize: 88 self.finalize(self.result) 89 90 def _slotFinished(self): 91 _runningJobs.discard(self) 92 if _pendingJobs: 93 _pendingJobs.pop().start() 94 self.running = False 95 self.done = True 96 self.finish() 97 98 99class SingleRun: 100 """Run a function in a background thread. 101 102 The outcome is silently discarded if another function is called before the 103 old one finishes. 104 105 """ 106 def __init__(self): 107 self._job = None 108 109 def cancel(self): 110 """Forgets the running job. 111 112 The job is not terminated but the callback is not called. 113 114 """ 115 j = self._job 116 if j: 117 j.finalize = None 118 self._job = None 119 120 def __call__(self, func, callback=None): 121 self.cancel() 122 j = self._job = Job() 123 j.work = func 124 def finalize(result): 125 self.cancel() 126 if callback: 127 callback(result) 128 j.finalize = finalize 129 j.start() 130 131 132def run(func, callback=None): 133 """Run specified function in a background thread. 134 135 The thread is immediately started. If a callback is specified, it is called 136 in the main thread with the result when the function is ready. 137 138 """ 139 j = Job() 140 j.work = func 141 j.finalize = callback 142 j.start() 143 144 145