前言
以前第一次使用线程的时候总是无法实现自己想要的效果,且为解决线程安全关闭的问题,今天看到一位老师讲了关于线程的使用方法,理解虽然还不是特别深刻,但已初步总结出相关注意事项。
环境:
- qt5.12.12
- windows操作系统
使用线程思路:
- 新建线程类,继承QThread
- 重写run()方法,将复杂程序都写在run的实现中,并设置循环条件(记得有些资料说线程必须写循环)
- 通过start()方法开启线程,通过requestInterruption()方法发出中断请求来跳出循环条件,跳出循环后该线程结束
- 在线程类中设置信号槽,当线程结束时杀死线程
- 在线程的析构函数中也要添加requestInterruption()中断请求,防止强制关闭窗口时线程不能安全关闭
先列举重要代码:(记得类要继承QThread)
AudioThread.h文件
void run() override;
AudioThread.cpp文件
//构造函数
AudioThread::AudioThread(QObject *parent) : QThread(parent)
{
// 当监听到线程结束时(finished),就调用deleteLater回收内存
connect(this,&AudioThread::finished,this,[=](){
this->deleteLater();
qDebug()<<"线程结束";
});
}
//析构函数
AudioThread::~AudioThread()
{
//强制关闭窗口时,线程也能安全关闭
requestInterruption();
wait();
qDebug()<<"析构函数";
}
void AudioThread::run()
{
.....自定义代码
while(!isInterruptionRequested()){//当没发出中断请求时,执行循环体
.....自定义代码
}
.....自定义代码
}
开启线程:
AudioThread *audio_thread=new AudioThread(this);
audio_thread->start();
关闭线程:
audio_thread->requestInterruption();
audio_thread->wait(); //等待线程结束才能执行下面代码
audio_thread=nullptr;
完整代码:(我这里是通过线程调用ffmpeg实现音频录制,看起来会有点复杂,可只关注重要代码,也不要盲目复制粘贴,运行之前需要先配置ffmpeg)
AudioThread.h文件
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include <QObject>
#include <QThread>
class AudioThread : public QThread
{
Q_OBJECT
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
private:
void run() override;
signals:
};
#endif // AUDIOTHREAD_H
AudioThread.cpp文件
#include "audiothread.h"
#include <QDebug>
#include <QFile>
extern "C"{
// 设备相关API
#include <libavdevice/avdevice.h>
// 格式相关API
#include <libavformat/avformat.h>
// 工具相关API(比如错误处理)
#include <libavutil/avutil.h>
// 编码相关API
#include <libavcodec/avcodec.h>
}
#ifdef Q_OS_WIN
// PCM文件的文件名
#define FILENAME "E:/media/out.pcm"
#else
#define FILENAME "/Users/mj/Desktop/out.pcm"
#endif
AudioThread::AudioThread(QObject *parent) : QThread(parent)
{
// 当监听到线程结束时(finished),就调用deleteLater回收内存
connect(this,&AudioThread::finished,this,[=](){
this->deleteLater();
qDebug()<<"线程结束";
});
}
AudioThread::~AudioThread()
{
//强制关闭窗口时,线程也能安全关闭
requestInterruption();
wait();
qDebug()<<"析构函数";
}
void AudioThread::run()
{
char *FMT_NAME="dshow";
const AVInputFormat *localAv_find_input_format = av_find_input_format(FMT_NAME);
if(!localAv_find_input_format){
qDebug()<<"找不到输入格式"<<FMT_NAME;
return;
}
qDebug()<<FMT_NAME;
AVFormatContext *ctx=nullptr;
char *device_name="audio=麦克风 (Realtek High Definition Audio)";
int ret = avformat_open_input(&ctx,device_name,localAv_find_input_format,nullptr);
if(ret!=0){
char errbuf[1024];
av_strerror(ret,errbuf,sizeof(errbuf));
qDebug()<<"打开设备失败"<<ret<<errbuf;
return;
}
QFile file(FILENAME);
if(!file.open(QIODevice::WriteOnly)){
qDebug()<<"文件打开失败"<<FILENAME;
avformat_close_input(&ctx);
return;
}
AVPacket *pkt = av_packet_alloc();
while(!isInterruptionRequested()){
//采集数据
ret = av_read_frame(ctx,pkt);
if(ret==0){
//向文件中写
file.write((char *)pkt->data,pkt->size);
qDebug()<<"正在录制,文件正在写入";
//释放资源
av_packet_unref(pkt);
}else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用
continue;
}
else{
char errbuf[1024];
av_strerror(ret,errbuf,sizeof(errbuf));
qDebug()<<"av_read_frame出错"<<errbuf<<sizeof (errbuf);
return;
}
av_packet_unref(pkt);
}
file.close();
//释放资源
av_packet_free(&pkt);
//关闭设备
avformat_close_input(&ctx);
qDebug()<<"录制成功结束";
return;
}
主线程调用子线程:(需提前定义全局变量AudioThread *audio_thread=nullptr;bool is_record,is_abnormal;)
void MainWindow::on_pushButton_clicked()
{
if(!is_record){
audio_thread=new AudioThread(this);
audio_thread->start();
//防止强制关闭窗口,线程无法正常退出
connect(audio_thread,&AudioThread::finished,audio_thread,[=](){
ui->pushButton->setText("开始录制");
is_record=false;
if(is_abnormal){
qDebug()<<"线程异常结束";
}
});
qDebug()<<"开始录制";
ui->pushButton->setText("结束录制");
is_record=true;
}else{
is_abnormal=false;
audio_thread->requestInterruption();
audio_thread->wait();
audio_thread=nullptr;
qDebug()<<"结束录制";
ui->pushButton->setText("开始录制");
is_record=false;
}
}
程序效果:
感觉QThread使用起来注意事项还是比较多的,后续有新的理解会继续补充