英文原文:http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/
我们之间经常使用IRC(网络聊天工具?)进行交流,社区也同样是这样。我在上 Freenode 网络的 QT 频道时会帮那些有问题的人解答。一个普遍的问题(也是我厌烦的问题)是如何理解QT多线程,还有如何使他们的代码工作。他们将他们的代码,或者基于他们代码的实例展示给我看,而我经常会回复他们一句:
你做错了。。。
我知道这个回答是有些无礼的,甚至是有些讽刺的。但我不禁想到,下面的这个类(假想)是一个不正确的多线程原则的应用,也是不正确的QT运用。
class MyThread : public QThread { public: MyThread() { moveToThread(this); } void run(); signals: void progress(int); void dataReady(QByteArray); public slots: void doWork(); void timeoutHandler(); };
我最不能忍受的代码是moveToThread(this);
我看到非常多的人在不理解这句话做了什么的情况下使用它。它做了什么?moveToThread
函数让QT确保事件句柄和扩展的信号和槽已经被指定的线程上下文调用,因为QThread是一个线程接口,所以我们让线程的代码运行在“它自己里面”,我们也会在线程运行之前做这样的事。即使这样似乎能工作,但它令人迷惑,而且这也不是QThread设计的使用方式(所有QThread的函数编写时是希望在线程创建时被调用,而不是QThread类初始化时)。
我想,moveToThread(this);
如此“恐怖的” (creeps) 写入到人们的代码中是因为他们看到有些博客也这么用。简单的网络搜索就会出现好几篇这样的博客,大家基本都是按照下面的方法做的:
- 继承QThread
- 添加一些消息和槽来完成工作
- 测试代码,发现那些槽并不是从“正确的线程”中调用的
- 问Google,找到
moveToThread(this);
方法,然后加了些注释:“当我添加这个的时候它似乎工作得很好
在我看来,问题出在第一步。QThread 是被设计来作为接口或者操作系统线程控制点(a control point to an operating system thread)使用的,不是一个放置你的多线程代码的地方。我们面向对象程序员继承一个类是为了扩展或者特殊化(specialize )基类的方法。我想,继承QThread的唯一正当的理由是添加QThread没有的方法,例如,也许要提供一个内存指针来实现线程栈,或者可能是添加实时接口支持。一段下载文件,查询数据库,或者任何其他处理的代码不应该继承QThread。它应该独立(encapsulated)在它自己的对象中。
通常,这意味着简单的从QThread修改你的代码到QObject,然后,可能需要修改你的类名。当你需要做些初始化时,你可以连接QThread的started信号。你需要实例化一个QThread并使用moveToThread(this);
函数指派你的对象到此线程中,让你的代码真正运行在新的线程上下文中。即使你仍然是使用moveToThread
函数让QT在指定的线程上下文中运行你的代码,但是我们保持了线程接口的独立性。如果有必要,现在就可能是时候将你的类的多个实例指派到一个单独的线程中,或者多个类的多个实例指派到一个单独的线程中了,换句话说,每个线程绑定一个实例也许是不必要的。(可以减少不必要的线程创建,译者注)
我常常埋怨那些混淆的线程相关的QT代码的编写。原始的QThread类是抽象的,所以继承是必要的。直到QT4.4,QThread::run()实现了一个默认的定义。在这之前,使用QThread唯一的方法是继承它。但是随着线程相关的添加和不同类别的对象之间的信号和槽的连接,我们突然有了一个方便的线程使用方法。我们喜欢简单,所以我们希望使用它。但不幸的是已经晚了。迫使人们继承QThread已经使它变得比预想的更难以改变。 我也埋怨没有一份最新的示例代码或者文档来告诉人们如何理由便捷的方法使他们从头疼中解脱出来。到现在为止,我发现的最好的资源是一篇我以前写的博客。
免责声明:上面你看到的所有内容是显而易见的观点。我做了很多关于这些类的工作,我很清楚的知道如何来使用或者如何不使用它们。