今天用qt5写多线程的时候遇到了一个报错Timers cannot be started from another thread,意思是我在一个线程中创建了QTimer对象,不能在另一个线程中start()该对象。
但是我觉得很奇怪,因为我的类中没有一个成员是QTimer或者其子类的,搞了半天才反应起来,可能是隐式的QTimer对象,也就是说,Qt内部在实现某种功能的时候使用了QTimer对象的start方法。
1.改法:
在我的代码中,我自定义一个AudioInput类,并在构造函数中new了一个audio对象,audio对象是一个QAudioInput类的对象。
在另一个类Widget的构造函数中我创建了new了一个AudioInput类的对象_ainput,并且使用moveToThread()将其放到一个新的子线程中执行。于是我发现,当我在
void AudioInput::startCollect()
{
inputdevice = audio->start();
connect(inputdevice, &QIODevice::readyRead, this, &AudioInput::onreadyRead);
}
函数中调用 inputdevice = audio->start();时就报错了 Timers cannot be started from another thread
这是原来的代码
AudioInput::AudioInput(QObject *parent)
: QThread(parent)
{
// 设置音频格式
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(8);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);
audio = new QAudioInput(format, this);
connect(audio,SIGNAL(stateChanged(QAudio::State)),
this,SLOT(handleStateChanged(QAudio::State)));
//测试
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
if (deviceInfo.deviceName().contains("input.pci")) {
info = deviceInfo;
break;
}
}
if (!info.isFormatSupported(format)) {
qWarning() << "Audio Input Format Useless!!!!!!!!!!!";
format = info.nearestFormat(format);
}
}
void AudioInput::startCollect()
{
inputdevice = audio->start();
connect(inputdevice, &QIODevice::readyRead, this, &AudioInput::onreadyRead);
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//音频
_ainput = new AudioInput();
_ainputThread = new QThread();
_ainput->moveToThread(_ainputThread);
_ainputThread->start();
QPushButton* button1 = new QPushButton(this);
button1->setText("开始录音");
QPushButton* button2 = new QPushButton(this);
button2->setText("结束录音");
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(button1);
mainLayout->addWidget(button2);
connect(button1, &QPushButton::clicked, _ainput, &AudioInput::startCollect);
connect(button2, &QPushButton::clicked, _ainput, &AudioInput::stopCollect);
resize(200,150);
}
这是修改之后的代码,主要的部分就是将AudioInput类的构造函数中本来new的audio对象放到了
startCollect函数中去new
AudioInput::AudioInput(QObject *parent)
: QThread(parent)
{
// 设置音频格式
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(8);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);
audio = nullptr; //修改之后的部分
//测试
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
if (deviceInfo.deviceName().contains("input.pci")) {
info = deviceInfo;
break;
}
}
if (!info.isFormatSupported(format)) {
qWarning() << "Audio Input Format Useless!!!!!!!!!!!";
format = info.nearestFormat(format);
}
}
void AudioInput::startCollect()
{
// 需要把audio的创建放到子线程中
if (audio == nullptr) { //修改之后的部分
qDebug() << "正在初始化";
audio = new QAudioInput(format, this);
connect(audio, SIGNAL(stateChanged(QAudio::State)),
this, SLOT(handleStateChanged(QAudio::State)));
}
qDebug() << "初始化完成";
inputdevice = audio->start();
connect(inputdevice, &QIODevice::readyRead, this, &AudioInput::onreadyRead);
}
2.原理
之前已经提到过,这个报错的本质就是在一个线程中创建了QTimer对象,但是在另一个线程中start这个对象,我之前的代码中,是在主线程中创建的QAudioInput对象,然后把其整个父类AudioInput对象放到了一条新的线程中,然后才使用信号触发了其槽函数,调用QAudioInput对象的start函数,因此报错,所以只要将创建和start函数的调用都放在同一个线程(新的线程)中就好了。