python - 在导出的 python 模块中使用异步 c++/qt 函数
问题描述
我有一个 dll 广泛依赖于 Qt 并导出某些返回 QFuture 的异步函数,例如
QFuture<QString> loadUserAsync();
我想导出这样的函数(通过pybind11),以便客户可以用python编写脚本。不过,我不想将异步 Qt 接口泄漏到 python 中,因此我正在用 C++ 编写一个包装 API,如下所示:
class API_EXPORT API {
public:
std::string loadUsername();
//...
};
std::string API::loadUsername() {
Future<QString> future = _core->loadUserAsync();
return future.result().toStdString();
}
然后通过pybind11导出:
py::class_<API>(m, "api")
.def(py::init<>())
.def("loadUsername", &API::loadUsername);
好吧,这有多个问题,我正在努力如何正确解决这个问题。
首先,我当然需要实例化 aQCoreApplication
,以便库中的信号/插槽和事件正常工作。这似乎可行,但我真的不确定这是否被认为是最佳实践以及是否必须调用该exec
函数(我不能exec
在调用线程上调用,否则它会阻塞):
API::API() {
if (!QCoreApplication::instance()) {
int argc = 1;
char* argv[] = {"api"};
_qt = std::make_shared<QCoreApplication>(argc, argv);
}
}
第二,future.result().toStdString();
死锁。我可以“修复”这个实例化我自己的QEventLoop
,但我不确定这是否是要走的路:
QFutureWatcher<void> watcher;
QEventLoop loop;
watcher.connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()), Qt::QueuedConnection);
watcher.setFuture(future);
loop.exec();
第三,在 dll a 中的某处QTimer
被实例化,因此我在 python 中打印出令人讨厌的警告,我很困惑如何处理它:
QObject::startTimer: Timers can only be used with threads started with QThread
解决方案
请让我先说我像瘟疫一样避免使用 Python。
你有没有在没有任何 Python 的情况下在 C++ 土地上工作?我问是因为:
std::string API::loadUsername() {
Future<QString> future = _core->loadUserAsync();
return future.result().toStdString();
}
不可能工作。
是的,我之前发表了评论。因为 Qt 是一个应用程序框架,所以不能“洒上一点 Qt”。它也是一个深受单线程影响的应用程序框架,因为在主事件循环之外几乎没有进行任何测试。他们还会抱怨运行多个事件循环是一种反模式。(这是应用现实,但这是一个不同的论点。)
QFuture 的全部意义在于使用线程池中的线程,并在该函数/任务成功或悲惨地结束时发出信号。
如果您的 return 语句实际上返回了您想要的值,那么您根本不需要 QFuture,因为您发生了阻塞 I/O。
顺便说一句,这个:
API::API() {
if (!QCoreApplication::instance()) {
int argc = 1;
char* argv[] = {"api"};
_qt = std::make_shared<QCoreApplication>(argc, argv);
}
}
考虑到所有当时古老的 x86 和较低处理器的知识,关于第一个参数是可执行文件的完整路径,这是令人难以置信的危险。在QCoreApplication中设置了一些东西,比如 applicationFilePath() 有点依赖它。
这是我在彩票跟踪器应用程序中使用 QFuture 的一些帖子,因为数据库 I/O 可能很长。
https://www.logikalsolutions.com/wordpress/information-technology/how-far-weve-come-pt-12/ https://www.logikalsolutions.com/wordpress/information-technology/how-far-weve- come-pt-13/ https://www.logikalsolutions.com/wordpress/information-technology/how-far-weve-come-pt-14/
https://www.logikalsolutions.com/wordpress/information-technology/how-far-weve-come-pt-16/
第 16 部分有我将在下面粘贴的代码,但您需要阅读第 12、13 和 14 部分才能理解目标。
void DataBaseIO::top12Report()
{
QFuture future = QtConcurrent::run(this, &DataBaseIO::detachedTop12);
}
void DataBaseIO::detachedTop12()
{
QString msgTxt;
QTextStream rpt(&msgTxt);
QSqlQuery q(db);
rpt << "Number hit_count" << endl
<< "--------- ---------" << endl;
rpt.setFieldWidth(9);
rpt.setFieldAlignment(QTextStream::AlignRight);
q.exec("select elm_no, count(*) as hit_count from drawings group by elm_no order by hit_count desc limit 12;");
while (q.next())
{
QSqlRecord rec = q.record();
int no = rec.field("elm_no").value().toInt();
int hits = rec.field("hit_count").value().toInt();
rpt << no << hits << endl;
}
rpt.flush();
emit displayReport( "Top 12 Report", msgTxt);
}
使用 QFuture 最简单的方法是通过 run()。不需要观察者。在你的任务结束时发出一个信号。诚然,您无法知道这是否会失败,因为您没有观察者。
顺便说一句,您的计时器来自 QFutureWatcher,因为它有一个 timerEvent()。 https://doc.qt.io/qt-5/qfuturewatcher-members.html
一旦你只在 C++ 中工作,你需要阅读 C++ 信号和 Python。
https://www.tutorialspoint.com/pyqt5/pyqt5_signals_and_slots.htm
https://www.mfitzp.com/tutorials/pyqt-signals-slots-events/
没有办法绕过应用程序必须有这样的东西。
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
app.exec()
语法可能并不完美,因为我避免使用 Python。
你不能只扔一点 Qt。谁使用你正在开发的东西,谁就必须使用整个框架。
Qt 有一个有限的子集不需要主事件循环。我找不到东西的清单。该项目可能不再发布它。现实情况是,如果没有主事件循环,您将无能为力。当您发出信号时,它必须有一个事件循环,以便它可以放在事件队列中(假设它不直接连接)。在 C++ 中,emit 在很多时候实际上是一个直接的函数调用。我不知道 Python 的世界。我认为它必须是一个排队事件。
根据经验,如果您使用任何具有信号的类,则必须运行一个主事件循环。如果您允许 Python 加入,那么 Python 必须启动主事件循环。如果没有,它就无法与 Qt 通信。
这是一个关于如何从 Python 使用 C/C++ 的非常详细的教程。 https://realpython.com/python-bindings-overview/
底线是,除非你在 Qt 上全力以赴,否则你无法做你想做的事。
很可能您可以使用纯 C++ 执行您尝试执行的操作,具体取决于您将 loadUserAsync() 重写为无任何 Qt 的直接 I/O 阻塞函数的能力。
推荐阅读
- docker - Kubernetes - 在命名空间和服务之间进行通信
- android - 如何创建圆角六边形?
- angularjs - 禁用单击选项卡。想要通过点击标签来停止切换标签
- ios - Interface Builder 以级联方式显示所有@IBInspectable
- docker - 在 gitlab runner 中使用 docker image 执行 shell 脚本失败
- apache-spark - 如何将数据框中的数据写入单个 .parquet 文件(单个文件中的数据和元数据)到 Amazon S3?
- javascript - CamanJS 单个通道饱和度,如 Photoshop/GIMP
- zigbee - 每个 Zigbee 模块是否都支持 868MHz
- apache - 如何在apache2中允许put方法
- javascript - 如何使用 npm 编译 JavaScript 文件并在静态 HTML 网站中使用?