Qt:解决跨线程调用socket/IO类,导致报错的问题(socket notifiers cannot be enabled from another thread)

Qt有很多IO相关的类,比如说QTcpSocket、QFile,总的来说,在Qt的框架内使用,还是非常方便的。
但是用过其他框架IO类的人,可能有一个很不习惯,就是Qt的所有IO类,都不推荐或者不可以跨线程操作,不然就会报错,比如说操作QTcpSocket跨线程调用write接口,就会报错:

socket notifiers cannot be enabled from another thread

要解决这个问题,直观的说就是不要跨线程操作,网上也有很多类似的说明。
这也是有道理的,很多时候真的是设计问题导致的,因为设计失误出现了不应该有的跨线程操作。
当然也可以用信号和槽封装一下,但是这样会涉及很多不必要的代码,我个人觉得也太过于麻烦。
那么我这里就提供一个更简单的方法,对QTcpSocket跨线程调用代码如下:

QMetaObject::invokeMethod( &socket, std::bind( static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ), &socket, QByteArray( "xxxx" ) ) );

这个比 socket->write( QByteArray( "xxxx" ) ); 看起来麻烦很多,不过也算是一行代码搞定了。

来分析一下这个 invokeMethod 调用,接口的定义是这样的

bool invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)
  • context:表示被调用的函数要在 哪个对象 的生存线程运行
  • function:被调用的函数

主要看这两个,后面都有缺省值,不用管。

在本例中context指定socket,就表示在socket的生存线程运行,这可能是任何线程,取决于你在哪里实例化这个socket。如果填写qApp,就表示指定在主线程运行。
function被赋值了一个std:bind,这是因为write不是槽函数,使用起来还是有点麻烦,不能直接写名字走moc系统。所以要手动用sid::bind把函数给包起来。

关于这个std::bind的3部分:
std::bind( static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ), &socket, QByteArray( "xxxx" ) )

static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ):QTcpSocket中,叫write的有很多个,所以要依靠 qint64(QTcpSocket::*)(const QByteArray &) 去指定出来是哪一个。这是C++的方法,和Qt无关。
&socket:表示要执行谁的write,有点类似于指针的角色
QByteArray( "xxxx" ):调用write时给的参数


除了IO相关的类,其他有一些Qt的类也不可以跨线程操作,比如说QTimer,也会报错

QObject::startTimer: Timers cannot be started from another thread

按照上面说的调用原理,可以这样写:

QMetaObject::invokeMethod( &timer, std::bind( static_cast< void(QTimer::*)(int) >( &QTimer::start ), &timer, 1000 ) );

对了,start是一个槽函数,所以如果借助moc系统的话,可以这样写(两个写法是等价的)

QMetaObject::invokeMethod( &timer, "start", Q_ARG( int, 1000 ) );

注意!在QMetaObject::invokeMethod配合std::bind使用的时候,5.10.0版本的Qt会有内存泄漏,bug如下:
https://bugreports.qt.io/browse/QTBUG-65462

请注意你的Qt版本,以及bug的修复情况,酌情使用这个方法。

你可能感兴趣的:(Qt)