PyQt:使用线程访问对象
我目前正在开发一个 小型 markdown 编辑器,但我有一个问题:
虽然速度非常快, markdown 模块不能发挥魔法,并且因为每次更改时它都会处理整个文本,因此当按住退格键时,程序将变得无响应。
如何使用线程(子进程,QThread)来实现类似以下内容?
- 当处理过程中更改文本时,作业将被推送到队列中。如果队列已包含一个作业,则该作业将被新作业替换。
- 当更改文本并且队列中没有作业时,将执行新作业。
- 当作业完成时,将执行队列中的作业。如果没有,我们就完成了。
澄清
我并不想要上面的算法,而是一种实现持续但不缓慢的渲染类型的方法。我想在“后台”触发渲染作业,但一次一个,确保始终执行最新的渲染作业。
- 用户开始更改文本。
- 当前文本的渲染作业在另一个线程中启动。
- 一旦完成并且渲染的 HTML 替换了旧的 HTML,就确定是否仍然需要渲染,即是否刚刚渲染了最新文本或旧版本。
- 如果文本和渲染仍然不同步,请转到 2。
请注意,澄清上面的算法是一种实现此目的的方法,其中 3。(异步性的确定)被实现为 1-thread-queue 。另请注意,只有最新的工作才真正需要完成,但我也希望完成随机的中间工作,以防止立即插入大量新输入的内容。
最好的情况:在快速、连续地输入大型文档时,渲染视图每隔几个单词左右就会更新一次,而编辑器始终保持响应。
I’m currently working on a small markdown-editor, but I have a problem:
While being very fast, the markdown module can’t do magic, and because it processes the whole text every time something is changed, the program will become unresponsive when e.g. holding down backspace.
How can I use threading (subprocess, QThread) to achieve something like the following?
- When changing text while processing takes place, the job is pushed onto the queue. If a Queue already contains a job, this job is replaced by the new one.
- When changing text and no job is on the Queue, the new job is executed.
- When a job finishes, the job from the queue is executed. If none is there, we are done.
Clarification
I don’t want exactly the above algorithm, but rather a way to achieve a constantly-but-not-slowing type of rendering. I want to fire the rendering jobs in the “background”, but one at a time, ensuring, that always the newest possible is executed.
- The user starts changing the text.
- A rendering job of the current text is started in another thread.
- As soon as it finishes and the rendered HTML has replaced the old HTML, it is determined if there is still rendering to be done, i.e. if the newest text was just rendered or an older version.
- If text and rendering is still out of sync, goto 2.
Note that the algorithm above the Clarification is a way to do that, with 3. (determination of asynchronity) being inplemented as 1-thread-queue. Also note that only the newest job really has to be done, but I also want random intermediate jobs to be finished to prevent a huge chunk of newly-typed content to be inserted at once.
Best case scenario: while rapidly and continuously typing on a huge document, the rendering view is updated every few words or so, while the editor is responsive all the time.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
对我来说,这看起来像是
QThreadPool
和QRunnable
的工作:QRunnable
派生的类,描述“渲染作业”QThreadPool
,其中maxThreadCount
设置为 1(非常重要,因为一次只能有一个渲染线程)。autoDelete
),并使用QThreadPool.start()
将其添加到线程池中。QThreadPool
维护自己的待处理作业队列,因此,如果当前有另一个作业正在运行,新作业也不会丢失。在这种情况下,一旦当前作业完成,就会执行它。要限制创建的渲染作业的数量,您不应直接在
.textChanged
信号上启动作业,而应通过QTimer
间接启动作业。每次文本更改时,如果计时器实际发出.timeout
,则以 500 毫秒的超时时间重新启动计时器,并且仅启动新的“渲染作业”。在实现“渲染作业”类时,请记住不要直接访问任何 GUI 类。相反,在“渲染作业”类中定义一个信号,渲染完成后,该信号以渲染的 HTML 字符串作为参数发出,并将其与显示小部件的
.setText()
连接。这并不完全是您所要求的,因为正在运行的渲染作业永远不会中断,而是始终完成。因此,如果文本变化非常快(例如用户按住退格键),预览可能会在一定程度上不同步。但是,我认为这是解决您的问题的最简单、最直接的解决方案,因为它不需要锁定、同步或跟踪当前正在运行的作业。每个作业都以“即发即忘”的方式真正异步运行。
To me this looks like a job for
QThreadPool
andQRunnable
:QRunnable
, describing a "render job"QThreadPool
withmaxThreadCount
set to 1 (very important, because there shall only be a single rendering thread at a time).autoDelete
enabled), and add it to the thread pool withQThreadPool.start()
.QThreadPool
maintains its own queue of pending jobs, thus the new job isn't lost, if there is another job currently running. In this case, it is executed once the current jobs is finished.To limit the number of rendering jobs created, you should not start jobs directly on
.textChanged
signals, but instead indirectly through aQTimer
. Each time, the text changes, restart your timer with something like a 500 ms time out and only start a new "render job", if the timer actually emits.timeout
.When implementing the "render job" class, remember to not access any GUI class directly. Instead, defined a signal in your "render job" class, which is emitted with the rendered HTML string as argument, once the rendering is finished and connect it with
.setText()
of your display widget.This is not exactly what you asked for, because a running render job is never interrupted, but instead always completes. Thus the preview can get out-of-sync to a certain degree, if the text changes very rapidly (like the user holding down backspace). However, I think that is the simplest and most straight-forward solution to your problem, because it doesn't need neither locking nor synchronisation nor tracking of currently running jobs. Each jobs runs truly asynchronously, in a "fire-and-forget" like style.
在每次文本更改时执行以下操作怎么样:检查渲染线程是否已经工作(标记),如果没有:让它渲染文本编辑器内容的副本。渲染线程在每次渲染结束时根据其最后输入检查编辑器的当前内容。如果存在差异:重新渲染。
What about doing the following at every text change: Check if the rendering thread already works (flag) and if not: let it render a copy of the text editor content. And the rendering thread checks the current content of the editor against its last input at the end of every rendering. If there is a difference: rerender.