def start(self): """Start this worker in a thread and add it to the global threadpool. The order of method calls when starting a worker is: .. code-block:: none calls QThreadPool.globalInstance().start(worker) | triggered by the QThreadPool.start() method | | called by worker.run | | | V V V worker.start -> worker.run -> worker.work """ if self in WorkerBase._worker_set: raise RuntimeError( trans._( 'This worker is already started!', deferred=True, )) # This will raise a RunTimeError if the worker is already deleted repr(self) WorkerBase._worker_set.add(self) self.finished.connect(lambda: WorkerBase._worker_set.discard(self)) QThreadPool.globalInstance().start(self)
def load_workspaces(self, row_index_pair, get_states_func): self._worker = Worker(self._load_workspaces_on_thread, row_index_pair, get_states_func) self._worker.signals.finished.connect(self.on_finished) self._worker.signals.error.connect(self.on_error) QThreadPool.globalInstance().start(self._worker)
def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class RunTask(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return RunTask() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs))
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Momentarily choose to load the ui from file for flexibility during # development, eventually it could be converted in a .py if stable. uic.loadUi("test_serial.ui", self) self.refreshButton.clicked.connect(self.handle_refresh_button) self.connectButton.clicked.connect(self.handle_connect_button) self.disconnectButton.clicked.connect(self.handle_disconnect_button) axes = self.canvas.figure.add_subplot(1, 1, 1) line2, = axes.plot([], []) # Temporarily create the figure here and pass it to the view thread. # fig = plt.figure() # ax = fig.add_subplot(111) # ax.autoscale() # line1, = ax.plot([], []) # fig.show() # Visualization Worker Thread, started as soon as # the thread pool is started. Pass the figure to plot on. # self.viewWo = ViewWorker(self.serialRxQu, fig, line1) # debug with an external figure. self.viewWo = ViewWorker(self.serialRxQu, self.canvas.figure, line2) self.threadpool = QThreadPool() self.threadpool.start(self.viewWo)
def test_that_load_workspaces_emits_row_processed_signal_after_each_row( self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() states = {0: mock.MagicMock()} errors = {} get_states_mock = mock.MagicMock() get_states_mock.return_value = states, errors self.batch_process_runner.load_workspaces( row_index_pair=self._mock_rows, get_states_func=get_states_mock) QThreadPool.globalInstance().waitForDone() self.assertEqual( 3, self.batch_process_runner.row_processed_signal.emit.call_count) self.batch_process_runner.row_processed_signal.emit.assert_any_call( 0, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call( 1, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call( 2, [], []) self.assertEqual( self.batch_process_runner.row_failed_signal.emit.call_count, 0)
def process_states(self, states, use_optimizations, output_mode, plot_results, output_graph, save_can=False): self._worker = Worker(self._process_states_on_thread, states, use_optimizations, output_mode, plot_results, output_graph, save_can) self._worker.signals.finished.connect(self.on_finished) self._worker.signals.error.connect(self.on_error) QThreadPool.globalInstance().start(self._worker)
def test_that_process_states_emits_row_failed_signal_after_each_failed_row( self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() self.sans_batch_instance.side_effect = Exception('failure') get_states_mock = mock.MagicMock() states = {0: mock.MagicMock()} errors = {} get_states_mock.return_value = states, errors self.batch_process_runner.process_states( row_index_pair=self._mock_rows, get_states_func=get_states_mock, use_optimizations=False, output_mode=OutputMode.BOTH, plot_results=False, output_graph='') QThreadPool.globalInstance().waitForDone() self.assertEqual( 3, self.batch_process_runner.row_failed_signal.emit.call_count) self.batch_process_runner.row_failed_signal.emit.assert_any_call( 0, 'failure') self.batch_process_runner.row_failed_signal.emit.assert_any_call( 1, 'failure') self.batch_process_runner.row_failed_signal.emit.assert_any_call( 2, 'failure') self.assertEqual( self.batch_process_runner.row_processed_signal.emit.call_count, 0)
def __save_snapshot(self): ''' Triggers the snapshot save job. ''' runner = SnapshotSaver(int(self.snapSlotSelector.currentText()), self.preferences, lambda: self.__capture_snap(convert=False), self.__measurement_store, self.snapshot_saved) QThreadPool.globalInstance().start(runner)
def change_pool_size(self, size): ''' Changes the pool size. :param size: size. ''' logger.info(f"Changing thread pool size to {size}") QThreadPool.globalInstance().setMaxThreadCount(size)
def set_max_thread_count(num: int): """Set the maximum number of threads used by the thread pool. Note: The thread pool will always use at least 1 thread, even if maxThreadCount limit is zero or negative. """ QThreadPool.globalInstance().setMaxThreadCount(num)
class MainWindow(QtWidgets.QMainWindow): serialRxQu = Queue() # serial FIFO RX Queue serialWo = SerialWorker(serialRxQu) # serial Worker Thread def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Momentarily choose to load the ui from file for flexibility during # development, eventually it could be converted in a .py if stable. uic.loadUi("test_serial.ui", self) self.refreshButton.clicked.connect(self.handle_refresh_button) self.connectButton.clicked.connect(self.handle_connect_button) self.disconnectButton.clicked.connect(self.handle_disconnect_button) axes = self.canvas.figure.add_subplot(1, 1, 1) line2, = axes.plot([], []) # Temporarily create the figure here and pass it to the view thread. # fig = plt.figure() # ax = fig.add_subplot(111) # ax.autoscale() # line1, = ax.plot([], []) # fig.show() # Visualization Worker Thread, started as soon as # the thread pool is started. Pass the figure to plot on. # self.viewWo = ViewWorker(self.serialRxQu, fig, line1) # debug with an external figure. self.viewWo = ViewWorker(self.serialRxQu, self.canvas.figure, line2) self.threadpool = QThreadPool() self.threadpool.start(self.viewWo) def handle_refresh_button(self): """Get list of serial ports available.""" ls = self.serialWo.get_port_list() if ls: print(ls) self.serialPortsComboBox.clear() self.serialPortsComboBox.addItems(ls) else: print('No serial ports available.') self.serialPortsComboBox.clear() def handle_connect_button(self): """Connect button opens the selected serial port and creates the serial worker thread. If the thread was already created previously and paused, it revives it.""" print(self.serialPortsComboBox.currentText()) self.serialWo.open_port(self.serialPortsComboBox.currentText()) if self.serialWo.no_serial_worker: self.serialWo.revive_it() self.threadpool.start(self.serialWo) self.serialWo.thread_is_started() if self.serialWo.is_paused: self.serialWo.revive_it() def handle_disconnect_button(self): """Disconnect button closes the serial port.""" self.serialWo.close_port()
def disconnect(self): ''' Disconnects the listener if we have one. ''' if self.__listener is not None: logger.info(f"Disconnecting from {self.ip_address}") self.__listener.kill() self.__listener = None QThreadPool.globalInstance().releaseThread() logger.info(f"Disconnected from {self.ip_address}")
def updateBeq(self): ''' Pulls or clones the named repository ''' from model.minidsp import RepoRefresher refresher = RepoRefresher(self.beqFiltersDir.text(), self.__get_beq_repos()) refresher.signals.on_end.connect( lambda: self.__count_and_return_cursor()) QThreadPool.globalInstance().start(refresher)
def show_release_notes(self): ''' Shows the release notes ''' QThreadPool.globalInstance().start( VersionChecker(self.preferences.get(SYSTEM_CHECK_FOR_BETA_UPDATES), self.__alert_on_old_version, self.__alert_on_version_check_fail, self.__version, signal_anyway=True))
def clear_filter_from_minidsp(self, slot=None): ''' Sets the config to bypass. ''' fp = FilterPublisher( [], slot, self.__minidsp_rs_exe, self.__minidsp_rs_options, lambda c: self.__on_send_filter_event(c, self.bypassMinidspButton)) QThreadPool.globalInstance().start(fp)
def __init__(self, parent, prefs, filter_loader): super(CatalogueDialog, self).__init__(parent=parent) self.__filter_loader = filter_loader self.__preferences = prefs minidsp_rs_path = prefs.get(BINARIES_MINIDSP_RS) self.__minidsp_rs_exe = None if minidsp_rs_path: minidsp_rs_exe = os.path.join(minidsp_rs_path, 'minidsp') if os.path.isfile(minidsp_rs_exe): self.__minidsp_rs_exe = minidsp_rs_exe else: minidsp_rs_exe = os.path.join(minidsp_rs_path, 'minidsp.exe') if os.path.isfile(minidsp_rs_exe): self.__minidsp_rs_exe = minidsp_rs_exe self.__minidsp_rs_options = None if self.__minidsp_rs_exe: self.__minidsp_rs_options = prefs.get(MINIDSP_RS_OPTIONS) self.__catalogue = {} self.setupUi(self) self.sendToMinidspButton.setMenu( self.__make_minidsp_menu(self.send_filter_to_minidsp)) self.bypassMinidspButton.setMenu( self.__make_minidsp_menu(self.clear_filter_from_minidsp)) self.__beq_dir = self.__preferences.get(BEQ_DOWNLOAD_DIR) self.__db_csv_file = os.path.join(self.__beq_dir, 'database.csv') self.__db_csv = {} self.browseCatalogueButton.setEnabled(False) self.browseCatalogueButton.setIcon(qta.icon('fa5s.folder-open')) QThreadPool.globalInstance().start( DatabaseDownloader(self.__on_database_load, self.__alert_on_database_load_error, self.__db_csv_file)) self.loadFilterButton.setEnabled(False) self.showInfoButton.setEnabled(False) self.openAvsButton.setEnabled(False) self.openCatalogueButton.setEnabled(False) for r in self.__preferences.get(BEQ_REPOS).split('|'): self.__populate_catalogue(r) years = SortedSet({c.year for c in self.__catalogue.values()}) for y in reversed(years): self.yearFilter.addItem(y) self.yearMinFilter.setMinimum(int(years[0])) self.yearMinFilter.setMaximum(int(years[-1]) - 1) self.yearMaxFilter.setMinimum(int(years[0]) + 1) self.yearMaxFilter.setMaximum(int(years[-1])) self.yearMinFilter.setValue(int(years[0])) self.filter_min_year(self.yearMinFilter.value()) self.yearMaxFilter.setValue(int(years[-1])) self.filter_max_year(self.yearMaxFilter.value()) content_types = SortedSet( {c.content_type for c in self.__catalogue.values()}) for c in content_types: self.contentTypeFilter.addItem(c) self.filter_content_type('') self.totalCount.setValue(len(self.__catalogue))
def send_filter_to_minidsp(self, slot=None): ''' Sends the currently selected filter to the filter publisher. ''' beq = self.__get_beq_from_results() filt = load_filter_file(beq.filename, 96000) fp = FilterPublisher( filt, slot, self.__minidsp_rs_exe, self.__minidsp_rs_options, lambda c: self.__on_send_filter_event(c, self.sendToMinidspButton)) QThreadPool.globalInstance().start(fp)
def process_states(self, row_index_pair, get_states_func, use_optimizations, output_mode, plot_results, output_graph, save_can=False): self._worker = Worker(self._process_states_on_thread, row_index_pair=row_index_pair, get_states_func=get_states_func, use_optimizations=use_optimizations, output_mode=output_mode, plot_results=plot_results, output_graph=output_graph, save_can=save_can) self._worker.signals.finished.connect(self.on_finished) self._worker.signals.error.connect(self.on_error) QThreadPool.globalInstance().start(self._worker)
def test_that_process_states_emits_row_processed_signal_after_each_row(self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() self.batch_process_runner.process_states(self.states, False, OutputMode.Both, False, '') QThreadPool.globalInstance().waitForDone() self.assertEqual(self.batch_process_runner.row_processed_signal.emit.call_count, 3) self.batch_process_runner.row_processed_signal.emit.assert_any_call(0, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(1, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(2, [], []) self.assertEqual(self.batch_process_runner.row_failed_signal.emit.call_count, 0)
def test_that_process_states_emits_row_processed_signal_after_each_row(self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() self.batch_process_runner.process_states(self.states, False, OutputMode.Both, False, '') QThreadPool.globalInstance().waitForDone() self.assertEqual(self.batch_process_runner.row_processed_signal.emit.call_count, 3) self.batch_process_runner.row_processed_signal.emit.assert_any_call(0, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(1, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(2, [], []) self.assertEqual(self.batch_process_runner.row_failed_signal.emit.call_count, 0)
def test_that_load_workspaces_emits_row_processed_signal_after_each_row(self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() self.batch_process_runner.load_workspaces(self.states) QThreadPool.globalInstance().waitForDone() self.assertEqual(self.batch_process_runner.row_processed_signal.emit.call_count, 3) self.batch_process_runner.row_processed_signal.emit.assert_any_call(0, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(1, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(2, [], []) self.assertEqual(self.batch_process_runner.row_failed_signal.emit.call_count, 0)
def __init__(self, parent, prefs, cache_dir, repo, img): super(ImageViewerDialog, self).__init__(parent=parent) self.__img = img self.__preferences = prefs self.__pm = None self.setupUi(self) self.scrollArea.setWidgetResizable(True) self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.label.setScaledContents(True) QThreadPool.globalInstance().start( ImgDownloader(cache_dir, repo, self.__on_load, self.__on_error, self.__img))
def execute(self): ''' Executes the command. ''' if self.__ffmpeg_cmd is not None: self.__extractor = AudioExtractor( self.__ffmpeg_cmd, port=self.__progress_port, cancel=self.__cancel, progress_handler=self.progress_handler, is_remux=self.__is_remux) QThreadPool.globalInstance().start(self.__extractor)
def test_that_load_workspaces_emits_row_processed_signal_after_each_row(self): self.batch_process_runner.row_processed_signal = mock.MagicMock() self.batch_process_runner.row_failed_signal = mock.MagicMock() self.batch_process_runner.load_workspaces(self.states) QThreadPool.globalInstance().waitForDone() self.assertEqual(self.batch_process_runner.row_processed_signal.emit.call_count, 3) self.batch_process_runner.row_processed_signal.emit.assert_any_call(0, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(1, [], []) self.batch_process_runner.row_processed_signal.emit.assert_any_call(2, [], []) self.assertEqual(self.batch_process_runner.row_failed_signal.emit.call_count, 0)
def test_that_process_states_calls_batch_reduce_for_specified_row(self): get_states_mock = mock.MagicMock() states = {0: mock.MagicMock()} errors = {} get_states_mock.return_value = states, errors self.batch_process_runner.process_states(row_index_pair=self._mock_rows, get_states_func=get_states_mock, use_optimizations=False, output_mode=OutputMode.BOTH, plot_results=False, output_graph='') QThreadPool.globalInstance().waitForDone() self.assertEqual(self.sans_batch_instance.call_count, 3)
def __start_analysers(self): for a in self.__analysers.values(): logger.info(f"Starting processor for {a.__class__.__name__}") QThreadPool.globalInstance().reserveThread() a.processor.start() def stop_processor(): logger.info(f"Stopping processor for {a.__class__.__name__}") a.processor.stop() QThreadPool.globalInstance().releaseThread() logger.info(f"Stopped processor for {a.__class__.__name__}") self.app.aboutToQuit.connect(stop_processor) logger.info(f"Started processor for {a.__class__.__name__}")
def search(self): ''' Searches for files matching the filter in the input directory. ''' self.filter.setEnabled(False) self.__candidates = ExtractCandidates( self, self.__preferences.get(ANALYSIS_TARGET_FS)) self.__preferences.set(EXTRACTION_BATCH_FILTER, self.filter.text()) globs = self.filter.text().split(';') job = FileSearch(globs) job.signals.started.connect(self.__on_search_start) job.signals.on_error.connect(self.__on_search_error) job.signals.on_match.connect(self.__on_search_match) job.signals.finished.connect(self.__on_search_complete) QThreadPool.globalInstance().start(job)
def doAction(self, mode): if not self.commandRunning: agilentAsync = AgilentAsync() agilentAsync.timerStatus.connect(self.debug) agilentAsync.started.connect(self.started) agilentAsync.finished.connect(self.finished) runnable = AgilentAsyncRunnable( agilentAsync, mode=mode, voltage=self.parameters.voltage, step_to_fixed_delay=self.parameters.delay, devices_selection=self.devices.getSelectedDevices(), ) QThreadPool.globalInstance().start(runnable)
def run(self): ''' Saves the snapshot to preferences. ''' start = time.time() dat = self.__capturer() logger.info(f"Captured in {to_millis(start, time.time())}ms") prefs = [] if len(dat.keys()) > 0: for ip, v in dat.items(): prefs.append( SetPreference(self.__preferences, f"{SNAPSHOT_GROUP}/{self.__id}/{ip}", v)) self.__signal.emit(self.__id, ip, v) for p in prefs: QThreadPool.globalInstance().start(p, priority=-1) logger.info(f"Saved snapshot in {to_millis(start, time.time())}ms")
def wait_for_workers_to_quit(msecs: int = None): """Ask all workers to quit, and wait up to `msec` for quit. Attempts to clean up all running workers by calling ``worker.quit()`` method. Any workers in the ``WorkerBase._worker_set`` set will have this method. By default, this function will block indefinitely, until worker threads finish. If a timeout is provided, a ``RuntimeError`` will be raised if the workers do not gracefully exit in the time requests, but the threads will NOT be killed. It is (currently) left to the user to use their OS to force-quit rogue threads. .. important:: If the user does not put any yields in their function, and the function is super long, it will just hang... For instance, there's no graceful way to kill this thread in python: .. code-block:: python @thread_worker def ZZZzzz(): time.sleep(10000000) This is why it's always advisable to use a generator that periodically yields for long-running computations in another thread. See `this stack-overflow post <https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread>`_ for a good discussion on the difficulty of killing a rogue python thread: Parameters ---------- msecs : int, optional Waits up to msecs milliseconds for all threads to exit and removes all threads from the thread pool. If msecs is `None` (the default), the timeout is ignored (waits for the last thread to exit). Raises ------ RuntimeError If a timeout is provided and workers do not quit successfully within the time allotted. """ for worker in WorkerBase._worker_set: worker.quit() msecs = msecs if msecs is not None else -1 if not QThreadPool.globalInstance().waitForDone(msecs): raise RuntimeError( trans._( "Workers did not quit gracefully in the time allotted ({msecs} ms)", deferred=True, msecs=msecs, ))
def process_files(self): ''' Creates the output content. ''' self.__preferences.set(BEQ_CONFIG_FILE, self.configFile.text()) self.__preferences.set(BEQ_MERGE_DIR, self.outputDirectory.text()) self.__preferences.set(BEQ_MINIDSP_TYPE, self.minidspType.currentText()) self.__preferences.set(BEQ_EXTRA_DIR, self.userSourceDir.text()) if self.__clear_output_directory(): self.filesProcessed.setValue(0) optimise_filters = False if MinidspType.parse(self.minidspType.currentText()).is_fixed_point_hardware(): result = QMessageBox.question(self, 'Are you feeling lucky?', f"Do you want to automatically optimise filters to fit in the 6 biquad limit? \n\n" f"Note this feature is experimental. \n" f"You are strongly encouraged to review the generated filters to ensure they are safe to use.\n" f"USE AT YOUR OWN RISK!\n\n" f"Are you sure you want to continue?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) optimise_filters = result == QMessageBox.Yes self.__start_spinning() self.errors.clear() self.errors.setEnabled(False) self.copyErrorsButton.setEnabled(False) self.optimised.clear() self.optimised.setEnabled(False) self.copyOptimisedButton.setEnabled(False) self.optimised.setVisible(optimise_filters) self.copyOptimisedButton.setVisible(optimise_filters) self.optimisedLabel.setVisible(optimise_filters) QThreadPool.globalInstance().start(XmlProcessor(self.__beq_dir, self.userSourceDir.text(), self.outputDirectory.text(), self.configFile.text(), self.minidspType.currentText(), self.__on_file_fail, self.__on_file_ok, self.__on_complete, self.__on_optimised, optimise_filters))
def __init__(self, parent, preferences): super(BatchExtractDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint) self.__candidates = None self.__preferences = preferences self.__search_spinner = None default_output_dir = self.__preferences.get(EXTRACTION_OUTPUT_DIR) if os.path.isdir(default_output_dir): self.outputDir.setText(default_output_dir) filt = self.__preferences.get(EXTRACTION_BATCH_FILTER) if filt is not None: self.filter.setText(filt) self.outputDirPicker.setIcon(qta.icon('fa5s.folder-open')) self.statusBar = QStatusBar() self.verticalLayout.addWidget(self.statusBar) try: core_count = QThreadPool.globalInstance().maxThreadCount() self.threads.setMaximum(core_count) self.threads.setValue(core_count) except Exception as e: logger.warning(f"Unable to get cpu_count()", e)
class WorkHandler(object): """ Class to handle threading of "work" (concretely this is just the evaluation of a function of the form func(*args, **kwargs). The process ID identifies (uniquely) each instance of a Worker; but the caller also defines an ID which is then used to identify the Worker through the API. """ class WorkListener(with_metaclass(ABCMeta, object)): """ This abstract base class defines methods which must be overriden and which handle responses to certain worker actions such as raised errors, or completion. """ def __init__(self): pass @abstractmethod def on_processing_finished(self, result): pass @abstractmethod def on_processing_error(self, error): pass def __init__(self): self.thread_pool = None self._listener = {} self._worker = {} self.thread_pool = QThreadPool() def _add_listener(self, listener, process_id, id): if not isinstance(listener, WorkHandler.WorkListener): raise ValueError("The listener is not of type " "WorkListener but rather {}".format(type(listener))) self._listener.update({process_id: {'id': id, 'listener': listener}}) @Slot() def on_finished(self, process_id): if process_id not in self._worker: return result = self._worker.pop(process_id)['worker'].result self._listener.pop(process_id)['listener'].on_processing_finished(result) @Slot() def on_error(self, process_id, error): if process_id not in self._worker: return self._listener[process_id]['listener'].on_processing_error(error) def process(self, caller, func, id, *args, **kwargs): """ Process a function call with arbitrary arguments on a new thread. :param caller: ?? :param func: The function to be evaluated. :param id: An identifying integer for the task. :param args: args for func. :param kwargs: keyword args for func. """ self.remove_already_processing(id) process_id = uuid.uuid4() # Add the caller self._add_listener(caller, process_id, id) finished_callback = functools.partial(self.on_finished, process_id) error_callback = functools.partial(self.on_error, process_id) worker = Worker(func, *args, **kwargs) worker.signals.finished.connect(finished_callback) worker.signals.error.connect(error_callback) self._worker.update({process_id: {'id': id, 'worker': worker}}) self.thread_pool.start(self._worker[process_id]['worker']) def remove_already_processing(self, id): """ Remove workers with ID :param id: """ for key, process in list(self._listener.items()): if process['id'] == id: self._listener.pop(key) self._worker.pop(key) def wait_for_done(self): self.thread_pool.waitForDone() def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return self.__dict__ != other.__dict__
def __init__(self): self.thread_pool = None self._listener = {} self._worker = {} self.thread_pool = QThreadPool()
def load_workspaces(self, states): self._worker = Worker(self._load_workspaces_on_thread, states) self._worker.signals.finished.connect(self.on_finished) self._worker.signals.error.connect(self.on_error) QThreadPool.globalInstance().start(self._worker)
def test_that_process_states_calls_batch_reduce_for_each_row(self): self.batch_process_runner.process_states(self.states, False, OutputMode.Both, False, '') QThreadPool.globalInstance().waitForDone() self.assertEqual(self.sans_batch_instance.call_count, 3)