QtConcurrent::run()开辟的另一个线程中修改UI组件值时程序崩溃的解决方案

报错原因

我最近在做一个Dns测速软件,目前为止它长这样

image-20240128212134606

而问题就出现在这里,这个进度条

image-20240128212216306

我想实现的效果是每测试完一个IP就自动更新Progress,代码是这样写到(最后几行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void DnsSpeedTester::RunPing(int num) {
int rowCount = model->rowCount();
QMutex mutex; // 用于保护对 model 的访问的互斥锁

QList<QFuture<void>> futures; // 用于跟踪并发任务的 futures

for (int row = 0; row < rowCount; ++row) {
QFuture<void> future = QtConcurrent::run([=, &mutex]() {
CPing P;
QModelIndex index = model->index(row, 0);
QVariant data = index.data(Qt::DisplayRole);
QString tmp_IP = data.value<QString>();
QByteArray temp = tmp_IP.toLatin1();
char* IP = temp.data();

// 使用互斥锁保护对 model 的访问
{
QMutexLocker locker(&mutex);
model->setData(model->index(row, 2), "测量中");
}

int delay = P.pings(IP, num);


// 使用互斥锁保护对 model 的访问
{
QMutexLocker locker(&mutex);
if(delay>999999){
model->setData(model->index(row, 2), "请求超时");
//将超时的设置字体为红色
QColor color = QColor(Qt::red);
model->setData(model->index(row,2), color, Qt::ForegroundRole);
}
else{
model->setData(model->index(row, 2), delay);
//同上字体染色
QColor color;
if (delay < 50) {
color = QColor(98, 151, 52);
} else if (delay < 100) {
color = QColor(255, 150, 63);
} else {
color = QColor(255, 73, 73);
}
model->setData(model->index(row,2), color, Qt::ForegroundRole);
}

}

qDebug() << "行:" << row << " IP:" << IP << " 延迟:" << delay;
});

futures.append(future); // 存储 future 以便后续监控
PingedIP++;
Progress=PingedIP/AllIP;
ui->progressBar->setValue(Progress*100);

}

可以看见我这里使用QtConcurrent::run来防止线程阻塞,然后又尝试修改progressBar的值,然后程序就直接崩溃了,而且Debug全是汇编和内存地址,啥也看不懂,后来才知道:

在QtConcurrent::run中使用了lambda表达式,该表达式默认建立在被捕获变量的副本上运行。在你的代码中,你在lambda表达式中访问了ui的成员,这是不被允许的,因为在另一个线程中操作GUI对象可能会导致线程冲突或其他异常。

解决方案

那么这种问题要怎么解决呢?

答案就是使用信号和槽机制,使修改操作在原UI线程中操作

一下是我的修改

首先在头文件中定义一个信号和槽

1
2
3
4
signals:
void updateProgressBarSignal(int value);
public slots:
void onUpdateProgressBar(int value);

由于我这里是要修改progressbar的值,所以要一个int类型的值,然后再源文件中实现该槽函数

1
2
3
4
void DnsSpeedTester::onUpdateProgressBar(int value)
{
ui->progressBar->setValue(value);
}

最后将原来是直接修改的部分,改为发送一个信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
QFuture<void> future = QtConcurrent::run([=, &mutex]() {
CPing P;
QModelIndex index = model->index(row, 0);
QVariant data = index.data(Qt::DisplayRole);
QString tmp_IP = data.value<QString>();
QByteArray temp = tmp_IP.toLatin1();
char* IP = temp.data();

// 使用互斥锁保护对 model 的访问
{
QMutexLocker locker(&mutex);
model->setData(model->index(row, 2), "测量中");
}

int delay = P.pings(IP, num);
PingedIP++;
Progress=PingedIP/AllIP;
emit updateProgressBarSignal(Progress*100);
qDebug()<<Progress*100;

// 使用互斥锁保护对 model 的访问
{
QMutexLocker locker(&mutex);
if(delay>999999){
model->setData(model->index(row, 2), "请求超时");
//将超时的设置字体为红色
QColor color = QColor(Qt::red);
model->setData(model->index(row,2), color, Qt::ForegroundRole);
}
else{
model->setData(model->index(row, 2), delay);
//同上字体染色
QColor color;
if (delay < 50) {
color = QColor(98, 151, 52);
} else if (delay < 100) {
color = QColor(255, 150, 63);
} else {
color = QColor(255, 73, 73);
}
model->setData(model->index(row,2), color, Qt::ForegroundRole);
}

}

这样再运行就不会崩溃了

更新预告

我的DNS测速软件也快要写完了,给大家看一下演示效果,开发进度70%

开发进度

异步测试延迟

用户自定义测试次数

IP延迟排序

IP延迟染色

只显示测试通过/不通过

右键直接设置为本机DNS

界面UI美化

在线更新DNS列表