RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1532746
Accepted
Ingvar
Ingvar
Asked:2023-07-27 19:12:23 +0000 UTC2023-07-27 19:12:23 +0000 UTC 2023-07-27 19:12:23 +0000 UTC

使用 PostgreSQL API 时 Qt 界面冻结

  • 772

我需要帮助诊断问题:

有一个项目 Qt 5.14.2,它是在多平台变体中开发的。适用于 PostgreSQL 数据库(从 9.x 到 15.x)。使用MSVC 2022 x64编译器在Win 10 x64中进行直接开发,并在Linux x64上进行测试。该项目不使用内置的 Qt pgsql 插件(有原因 - 但它们并不那么重要)。相反,编写了一组自己的接口类,通过 libpq 与 Postgre 交互,这允许您通过 API 对数据库进行完全控制。

有一项任务:在后台加载一个视图的结果。查询中的记录数量达到约 5 万条,并且一直在增长,尽管速度不是很快。实现了一个附加线程 QThread,它与程序并行加载必要的内容。

问题:发生 2 次冻结:

  • 第一,当线程中请求数据时,这是可以理解的,本质上不会影响任何事情;
  • 第二个 - 在下载过程结束时,现在它将程序界面冻结。

问题在于第二次冻结,或者更确切地说,它冻结了整个程序的界面,尽管纯粹从技术上来说这是可以理解的。

那么详细信息:

  1. 正如已经说过的,有自己的与 PostgreSQL 交互的对象,它们可以根据我的需要工作。如果代码很重要,我稍后会给出。
  2. 使用一个对象创建 QThread 来提供负载:
     void Model::LoadData()
        {
            //-- запускаем поток, загрузки данных
            load_thread = new QThread(); //-- переменная определена в классе
            load_thread->setObjectName("BW_load");
            mtLoader = new ubiVulLoader(&DataList);
            mtLoader->moveToThread(load_thread);
            connect(mtLoader, SIGNAL(setProgressMax(int)), this, SLOT(setProgressMax(int)));
            connect(mtLoader, SIGNAL(setProgress(int)), this, SLOT(setProgress(int)), Qt::DirectConnection);
            connect(mtLoader, SIGNAL(endProcess()), this, SLOT(endProcess()));
            connect(load_thread, SIGNAL(started()), mtLoader, SLOT(doLoad()));
            load_thread->start();
        }

最后(在发出 endProcess 之后),处理以下代码:

void Model::endProcess()
{
    emit sendProcessEnd();
    disconnect(mtLoader, SIGNAL(setProgressMax(int)), this, SLOT(setProgressMax(int)));
    disconnect(mtLoader, SIGNAL(setProgress(int)), this, SLOT(setProgress(int)));
    disconnect(mtLoader, SIGNAL(endProcess()), this, SLOT(endProcess()));
    load_thread->quit();
    load_thread->wait(); //-- или deleteLater() - без разницы
    delete mtLoader;
    delete load_thread;
}

doLoad() 函数本身(缩写)如下所示:

void mtLoader::doLoad()
{
    auto con = new TConnector();
    if (con->Open())
    {
        if (con->transactionStart())
        {
            //-- ВСЕ ДЕЙСТВИЯ ВЫПОЛНЯЮТСЯ В РАМКАХ ОДНОЙ (!!!) ТРАНЗАКЦИИ
            /*
            Использование функций доступа по номеру поля а не по его имени существенно ускоряет
            обработку.  */

            QString q_load =
                QString("select " ... тут много полей по порядку
                        " from table ..."); 

            TQuery *qc = con->CreateNewQuery("select count(*) from table");
            long cnt = 0;
            if (qc->Execute())
            {
                cnt = qc->FieldByNum(0).toLongLong();
            }
            delete qc;
            if (cnt > 0)
            {
                emit setProgressMax(cnt); //-- оповещение о максимуме
                recs->resize(cnt); 
                //-- чтобы не перегружать событиями основной интерфейс искусственно снизим частоту оповещения
                //-- см. в конец цикла
                auto step = cnt / 100;
                auto st = 0;
                int progress = 0;

                TQuery *q = con->CreateNewQuery(q_load);
                if (q->Execute())
                {
                    int cnt = q->getRowCount();
                    TData *rec = nullptr;
                    for (int i = 0; i < cnt; i++)
                    {
                        recs->data()[i] = new TData();
                        rec = recs->at(i);
                        //-- служебные поля таблицы
                        rec->id = q->FieldByNum(0).toLongLong(); 
                        rec->code = q->FieldByNum(2).toString();
                        rec->name = q->FieldByNum(1).toString();
                        
                        ... ну и так далее заполняем объект

                        //--
                        q->Next();

                        //-- чтобы не перегружать событиями основной интерфейс искусственно снизим частоту оповещения
                        //-- оповещение о прогрессе будем отсылать 1 раз за 1 процент выполнения, а не на каждую запись
                        st += 1;
                        progress+=1;
                        if (st > step)
                        {
                            st = 0;
                            emit setProgress(progress);  //-- оповещение о прогрессе выполнения
                        }
                    }
                    emit setProgress(progress);  //-- оповещение о прогрессе выполнения
                }
                else
                {
                    //-- в случае ошибки ...
                    con->transactionRollback();
                    con->Close();
                    delete q;
                    delete con;
                    return;
                }
                delete q; //-- <<<< FREEZE
            }
        }
        con->transactionRollback();
        con->Close();
    }
    delete con;
    emit endProcess(); //-- оповещение об окончании процесса
}

第二次冻结发生在对象 q 被删除时,在此期间执行以下代码:

...
    if (_res!=nullptr)
    {
        PQclear(_res); //-- <<<< FREEZE
        _res=nullptr;
    }
    ...

冰点标记为“FREEZE”。在最后的代码中,在终止查询之前需要调用函数来修复内存泄漏 - 这是 PostgreSQL API 的要求。

从理智上来说,我明白清除一个我大小的内存块原则上不是一件很快的事情,但是:一切都是在单独的线程中完成的,整个程序的界面被冻结。

第一个想法:制作(并完成)一个 DataReader 类型的对象,逐行读取,但它的工作速度要慢很多,因为从服务器接收到的每个记录都会调用这样的清理函数(当使用行模式时)。

第二个想法:TConnector / TQuery 对象是简单的类,而不是 QObject 的后代。还没检查过。

无论如何,问题出现了:当在单独的线程中使用服务器时,调用其 API 函数会导致整个程序的界面冻结。为什么?

UPD 1:C# 具有可直接与 PostgreSQL 配合使用的 NpgSQL。他有一个DataReader,使用它在连接到同一数据库的另一个项目中就不存在这样的困难。这里无法重复。

请分享您的想法。

c++
  • 1 1 个回答
  • 45 Views

1 个回答

  • Voted
  1. Best Answer
    Ingvar
    2023-07-28T20:24:53Z2023-07-28T20:24:53Z

    所描述的问题消失了,只需在不调试的情况下构建项目,而是使用发布程序集即可。我决定取消订阅,你永远不知道谁会遇到类似的...:)

    • 0

相关问题

  • 编译器和模板处理

  • 指针。找到最小数量

  • C++,关于枚举类对象初始化的问题

  • 函数中的二维数组

  • 无法使用默认构造函数创建类对象

  • C++ 和循环依赖

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5