1 回答
TA贡献2012条经验 获得超12个赞
正如错误所解释的,您无法从另一个线程启动和停止 QTimer,并且由于 APScheduler 在不同的线程上工作,因此问题的原因:是self.hello从 APScheduler 线程调用的,而不是从创建计时器的线程调用的。
要访问在不同线程中创建的对象,您需要使用信号和槽,以便让 Qt 管理不同线程之间的通信。
因此,解决方案可能是通过继承 QObject 来子类化您的 Scheduler(以便能够创建信号并连接到它们),然后在每次执行作业时使用自定义信号并使用该信号重新启动计时器。
为了实现这一目标,我使用了一个createJob实际运行作业的函数,并started在作业启动时发出信号,并completed在作业完成时发出信号。
不幸的是,我无法测试以下代码,因为我现在无法安装 APScheduler,但逻辑应该没问题。
Main.py
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.sch = Scheduler()
self.sch.completed.connect(self.start_my_timer)
def hello(self):
print("hello world")
# no call to self.start_my_timer here!
Schedule.py
from datetime import datetime
from apscheduler.schedulers.qt import QtScheduler
from PyQt5 import QtCore
class Scheduler(QtCore.QObject):
started = QtCore.pyqtSignal(object)
completed = QtCore.pyqtSignal(object)
def __init__(self):
self.id = 'test_job'
self.sched = QtScheduler()
def add(self, job_function, *args, **kwargs):
self.sched.add_job(self.createJob(job_function), 'cron',
day_of_week='mon-fri', hour='9-18',
minute='2,7,12,17,22,27,32,37,42,47,52,57',
second='5', id=self.id, *args, **kwargs)
def createJob(self, job_function):
def func(*args, **kwargs):
self.started.emit(job_function)
job_function(*args, **kwargs)
self.completed.emit(job_function)
return func
def start(self):
self.sched.start()
def next_occurance(self):
for job in self.sched.get_jobs():
if job.id == self.id:
return job.next_run_time
请注意,我使用参数(在您的情况下是对作业的引用)发出started和completed信号,如果您想对多个作业做出不同的反应,这可能有助于识别作业。我还添加了对位置和关键字参数的基本支持。job_functionself.hello
另请注意,我仅提供一个非常基本的实现(您的函数仅打印一条消息)。如果您需要与作业功能中的 UI 元素进行交互,那么 QTimer 也会出现同样的问题,因为不允许从主 Qt 线程之外的线程访问 UI 元素。
在这种情况下,您需要寻找另一种方法。例如,您可以添加一个作业(实际上并非从调度程序运行)并以该作业作为参数发出信号,然后连接到将在主线程中实际运行该作业的函数。
Main.py
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.sch = Scheduler()
self.sch.startJob.connect(self.startJob)
def startJob(self, job, args, kwargs):
job(*args, **kwargs)
self.start_my_timer()
def hello(self):
self.someLabel.setText("hello world")
Schedule.py
from datetime import datetime
from apscheduler.schedulers.qt import QtScheduler
from PyQt5 import QtCore
class Scheduler(QtCore.QObject):
startJob = QtCore.pyqtSignal(object, object, object)
# ...
def add(self, job_function, *args, **kwargs):
self.sched.add_job(self.createJob(job_function, args, kwargs), 'cron',
day_of_week='mon-fri', hour='9-18',
minute='2,7,12,17,22,27,32,37,42,47,52,57',
second='5', id=self.id)
def createJob(self, job_function, args, kwargs):
def func():
self.starJob.emit(job_function, args, kwargs)
return func
如前所述,上面的代码未经测试,您需要检查可能的错误(也许我在通配符参数上犯了一些错误)。
最后,一些小建议:
实际上有必要使用装饰器的情况非常少
pyqtSlot
;有趣的是,使用它们通常是问题或意外行为的根源。通常最好保留信号参数原样而不进行任何转换,因此您不应将时间转换为信号的字符串
get_seconds
;setNum()
此外,QLabel 可以使用(浮点型和整数型)接受数值。对空格要更加小心(我指的是
self.sched.add_job
):对于关键字参数,空格只能存在于逗号之后;虽然它实际上并不代表问题,但它极大地提高了可读性。
添加回答
举报