PyQt 中带有 QThread 的后台线程
我有一个程序,通过我在 PyQt 中编写的 gui 与我正在使用的无线电进行交互。显然,无线电的主要功能之一是传输数据,但要连续执行此操作,我必须循环写入,这会导致 gui 挂起。由于我从未处理过线程,因此我尝试使用 QCoreApplication.processEvents() 来摆脱这些挂起。 不过,无线电需要在传输之间休眠,因此 gui 仍然根据这些挂起的时间长短而挂起。最后睡。
有没有一种简单的方法可以使用 QThread 来解决这个问题?我查找了有关如何使用 PyQt 实现多线程的教程,但其中大多数涉及设置服务器,并且比我需要的要先进得多。老实说,我什至不需要我的线程在运行时更新任何内容,我只需要启动它,让它在后台传输,然后停止它。
I have a program which interfaces with a radio I am using via a gui I wrote in PyQt. Obviously one of the main functions of the radio is to transmit data, but to do this continuously, I have to loop the writes, which causes the gui to hang. Since I have never dealt with threading, I tried to get rid of these hangs using QCoreApplication.processEvents().
The radio needs to sleep between transmissions, though, so the gui still hangs based on how long these sleeps last.
Is there a simple way to fix this using QThread? I have looked for tutorials on how to implement multithreading with PyQt, but most of them deal with setting up servers and are much more advanced than I need them to be. I honestly don't even really need my thread to update anything while it is running, I just need to start it, have it transmit in the background, and stop it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我创建了一个小示例,展示了处理线程的 3 种不同且简单的方法。我希望它能帮助您找到解决问题的正确方法。
I created a little example that shows 3 different and simple ways of dealing with threads. I hope it will help you find the right approach to your problem.
将此答案更新为 PyQt5,python 3.4
使用此模式来启动一个不获取数据并返回数据的工作程序,因为它们可用于表单。
1 - Worker类变得更小,并放在自己的文件worker.py中,以便于记忆和独立软件重用。
2 - main.py 文件是定义 GUI Form 类的文件
3 - 线程对象没有子类化。
4 - 线程对象和工作对象都属于表单对象
5 - 过程的步骤在注释中。
主要文件是:
Take this answer updated for PyQt5, python 3.4
Use this as a pattern to start a worker that does not take data and return data as they are available to the form.
1 - Worker class is made smaller and put in its own file worker.py for easy memorization and independent software reuse.
2 - The main.py file is the file that defines the GUI Form class
3 - The thread object is not subclassed.
4 - Both thread object and the worker object belong to the Form object
5 - Steps of the procedure are within the comments.
And the main file is:
根据 Qt 开发人员的说法,子类化 QThread 是不正确的(请参阅 http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/)。但那篇文章确实很难理解(而且标题有点居高临下)。我找到了一篇更好的博客文章,其中更详细地解释了为什么应该使用一种线程风格而不是另一种风格:http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
另外,我强烈推荐此视频来自 KDAB,介绍线程之间的信号和槽。
在我看来,您可能永远不应该为了重载 run 方法而子类化线程。虽然这确实有效,但你基本上绕过了 Qt 希望你如何工作。另外,您会错过诸如事件和适当的线程安全信号和槽之类的事情。另外,正如您可能在上面的博客文章中看到的那样,“正确”的线程方式迫使您编写更多可测试的代码。
以下是如何在 PyQt 中利用 QThreads 的几个示例(我在下面发布了一个单独的答案,正确使用 QRunnable 并合并信号/槽,如果您有很多需要负载平衡的异步任务,该答案会更好) 。
According to the Qt developers, subclassing QThread is incorrect (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/). But that article is really hard to understand (plus the title is a bit condescending). I found a better blog post that gives a more detailed explanation about why you should use one style of threading over another: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Also, I would highly recommend this video from KDAB on signals and slots between threads.
In my opinion, you should probably never subclass thread with the intent to overload the run method. While that does work, you're basically circumventing how Qt wants you to work. Plus you'll miss out on things like events and proper thread safe signals and slots. Plus as you'll likely see in the above blog post, the "correct" way of threading forces you to write more testable code.
Here's a couple of examples of how to take advantage of QThreads in PyQt (I posted a separate answer below that properly uses QRunnable and incorporates signals/slots, that answer is better if you have a lot of async tasks that you need to load balance).
Matt 提供了非常好的示例,我修复了拼写错误,而且 pyqt4.8 现在也很常见,因此我也删除了虚拟类并添加了 dataReady 信号的示例
Very nice example from Matt, I fixed the typo and also pyqt4.8 is common now so I removed the dummy class as well and added an example for the dataReady signal
在 PyQt 中,有很多用于获取异步行为的选项。对于需要事件处理的事情(即 QtNetwork 等),您应该使用我在该线程的其他答案中提供的 QThread 示例。但对于绝大多数线程需求,我认为该解决方案远远优于其他方法。
这样做的优点是 QThreadPool 将 QRunnable 实例安排为任务。这与Intel的TBB中使用的任务模式类似。它不像我喜欢的那么优雅,但它确实实现了出色的异步行为。
这使您可以通过 QRunnable 在 Python 中利用 Qt 的大部分线程功能,并且仍然可以利用信号和槽。我在多个应用程序中使用相同的代码,有些应用程序进行数百个异步 REST 调用,有些应用程序打开文件或列出目录,最好的部分是使用这种方法,Qt 任务为我平衡系统资源。
退出应用程序时,您需要确保取消所有任务,否则应用程序将挂起,直到每个计划任务完成
In PyQt there are a lot of options for getting asynchronous behavior. For things that need event processing (ie. QtNetwork, etc) you should use the QThread example I provided in my other answer on this thread. But for the vast majority of your threading needs, I think this solution is far superior than the other methods.
The advantage of this is that the QThreadPool schedules your QRunnable instances as tasks. This is similar to the task pattern used in Intel's TBB. It's not quite as elegant as I like but it does pull off excellent asynchronous behavior.
This allows you to utilize most of the threading power of Qt in Python via QRunnable and still take advantage of signals and slots. I use this same code in several applications, some that make hundreds of asynchronous REST calls, some that open files or list directories, and the best part is using this method, Qt task balances the system resources for me.
When exiting the application you'll want to make sure you cancel all of the tasks or the application will hang until every scheduled task has completed
基于其他答案中提到的 Worker 对象方法,我决定看看是否可以扩展解决方案以调用更多线程 - 在这种情况下,机器可以运行并以不确定的完成时间启动多个工作线程的最佳数量。
为此,我仍然需要子类化 QThread - 但仅分配一个线程号并“重新实现”信号“完成”和“开始”以包含它们的线程号。
我非常关注主 GUI、线程和工作人员之间的信号。
同样,其他人的答案也很费劲地指出不作为 QThread 的父级,但我认为这不是一个真正的问题。然而,我的代码也小心地破坏了 QThread 对象。
但是,我无法将工作对象设置为父对象,因此似乎需要在线程函数完成或 GUI 被破坏时向它们发送 deleteLater() 信号。我自己的代码因不这样做而挂起。
我认为必要的另一个增强功能是重新实现 GUI (QWidget) 的 closeEvent,以便指示线程退出,然后 GUI 将等待所有线程完成。当我研究这个问题的其他一些答案时,我得到了 QThread 损坏错误。
也许对其他人有用。我当然发现这是一个有用的练习。也许其他人会知道线程宣布其身份的更好方法。
以及下面的工人代码
Based on the Worker objects methods mentioned in other answers, I decided to see if I could expand on the solution to invoke more threads - in this case the optimal number the machine can run and spin up multiple workers with indeterminate completion times.
To do this I still need to subclass QThread - but only to assign a thread number and to 'reimplement' the signals 'finished' and 'started' to include their thread number.
I've focused quite a bit on the signals between the main gui, the threads, and the workers.
Similarly, others answers have been a pains to point out not parenting the QThread but I don't think this is a real concern. However, my code also is careful to destroy the QThread objects.
However, I wasn't able to parent the worker objects so it seems desirable to send them the deleteLater() signal, either when the thread function is finished or the GUI is destroyed. I've had my own code hang for not doing this.
Another enhancement I felt was necessary was was reimplement the closeEvent of the GUI (QWidget) such that the threads would be instructed to quit and then the GUI would wait until all the threads were finished. When I played with some of the other answers to this question, I got QThread destroyed errors.
Perhaps it will be useful to others. I certainly found it a useful exercise. Perhaps others will know a better way for a thread to announce it identity.
And the worker code below
PySide2 解决方案:
与 PyQt5 不同,在 PySide2 中,QThread.started 信号是在原始线程而不是工作线程上接收/处理的!幸运的是,它仍然接收工作线程上的所有其他信号。
为了匹配 PyQt5 的行为,您必须自己创建启动信号。
这是一个简单的解决方案:
PySide2 Solution:
Unlike in PyQt5, in PySide2 the QThread.started signal is received/handled on the original thread, not the worker thread! Luckily it still receives all other signals on the worker thread.
In order to match PyQt5's behavior, you have to create the started signal yourself.
Here is an easy solution: