开发者

PyQt: Access objects while using threading

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?

  1. 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.
  2. When changing text and no job is on the Queue, the new job is executed.
  3. When a job finishes, the job from the queue is executed. If none is there, we are done.

Clarification

I don’t want exactly th开发者_开发技巧e 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.

  1. The user starts changing the text.
  2. A rendering job of the current text is started in another thread.
  3. 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.
  4. 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.


To me this looks like a job for QThreadPool and QRunnable:

  • Create a class deriving from QRunnable, describing a "render job"
  • Create a QThreadPool with maxThreadCount set to 1 (very important, because there shall only be a single rendering thread at a time).
  • Each time the text changes, create an instance of your "render job" class (probably with autoDelete enabled), and add it to the thread pool with QThreadPool.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 a QTimer. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜