如何将Boost.Asio主循环集成到Qt4或GTK等GUI框架中
有没有办法将 Boost.Asio 与 Qt4(首选)或 GTK 主循环集成? GTK 提供类似 poll(2) 的 API,所以技术上应该是可行的。 Qt 提供了自己的网络层,但我更喜欢使用为 Boost.Asio 编写的现有代码。 我想不使用额外的线程来集成它们。
有没有关于如何为 Qt4(首选)或 GTKmm 执行此操作的参考?
谢谢。
编辑
我想澄清一些事情以使答案更容易。 Qt 和 GTKmm 都提供 “选择类似”功能:
- http://qt-project.org/doc /qt-5.0/qtcore/qsocketnotifier.html
- http://www.gtkmm.org/docs/glibmm-2.4/docs/reference/html/group__MainLoop.html
所以,问题是,如何将现有的“选择器/轮询器”作为反应器集成到 Boost.Asio io_service
。 如今,Boost.Asio 可以使用 select、kqueue、epoll、/dev/poll 和 iocp 作为reactor/proactor 服务。 我想将它集成到 GUI 框架的主循环中。
欢迎任何建议和解决方案(更好)。
Is there any way to integrate Boost.Asio with Qt4 (preferred) or GTK main loop?
GTK provides poll(2) like API so technically is should be possible. Qt provides its own networking layer, however I prefer to use existing code written for Boost.Asio.
I want to integrate them without using an additional thread.
Is there any reference how to do this for Qt4 (preferred) or GTKmm?
Thanks.
Edit
I want to clearify several things to make the answer easier. Both Qt and GTKmm provide
"select like" functionality:
- http://qt-project.org/doc/qt-5.0/qtcore/qsocketnotifier.html
- http://www.gtkmm.org/docs/glibmm-2.4/docs/reference/html/group__MainLoop.html
So, the question is, how to integrate existing "selectors/pollers" as reactor to
Boost.Asio io_service
. Today, Boost.Asio can use select, kqueue, epoll, /dev/poll and iocp as reactor/proactor service. I want to integrate it to the main-loop of GUI framework.
Any suggestions and solutions (better) are welcome.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
简单:
构建一个调用属于 gui 的 io_service::poll_one() 的 QT 插槽。 将该插槽连接到 QT 的
tick
信号。深入:
幸运的是,Boost.Asio 设计得非常好。 关于如何向底层异步内部提供执行线程有很多选择。 人们已经提到过使用 io_service::run() ,这是一种具有许多缺点的阻塞调用。
您只能从单个线程访问 GUI 小部件。 如果外部线程想要改变任何小部件,通常需要将事件发布到 gui。 这与 Asio 的工作方式非常相似。
最简单的方法是只指定一个线程(或计时器)来运行 io_service::run() 并让 Asio 完成处理程序发布 gui 信号。 这会起作用。
相反,您可以保证仅在 io_service 调用方的执行线程中调用完成处理程序。 不要让 gui 线程调用 io_service::run(),因为它正在阻塞并且可能会挂起 gui。 请改用
io_service::poll()
或io_service::poll_one()
。 这将导致从 gui 线程调用任何挂起的 Asio 完成处理程序。 由于处理程序在 GUI 线程中运行,因此它们可以自由地修改小部件。现在您需要确保 io_service 有机会定期运行。 我建议重复 gui 信号调用
poll_one()
几次。 我相信 QT 有一个刻度信号可以解决这个问题。 您当然可以滚动自己的 QT 信号以获得更多控制。Simple:
Build a QT slot that calls the
io_service::poll_one()
belonging to the gui. Connect that slot to QT'stick
signal.In Depth:
Luckily for you Boost.Asio is very well designed. There are many options on how to provide a thread of execution to the underlying asynchronous internals. People have already mention using
io_service::run()
, a blocking call with many disadvantages.You are only allowed to access gui widgets from a single thread. External threads generally need to post events to the gui if they want to do mutate any widget. This is very similar to how Asio works.
The naive approach is to just dedicate one thread (or timer) to running
io_service::run()
and have the Asio completion handler post a gui signal. This will work.Instead you can use the guarantee that completion handlers will only be called in the thread of execution of the
io_service
caller. Don't have the gui thread callio_service::run()
as it is blocking and could hang the gui. Instead useio_service::poll()
orio_service::poll_one()
. This will cause any pending Asio completion handlers to be called from the gui thread. Since the handlers are running in the gui thread they are free to modify the widgets.Now you need make sure the
io_service
gets a chance to run regularly. I recommend having a repeating gui signal callpoll_one()
a few times. I believe QT has a tick signal that would do the trick. You could of course roll your own QT signal for more control.这是一个相当老的问题,但对于那些现在正在阅读它的人,我想分享我的代码,它是 QAbstractEventDispatcher 的实现,用于提升: :亚洲。
您所需要的只是在创建 QApplication 之前添加以下行(通常在 main() 中)。
这将导致 io_service 与 qt 应用程序在一个线程中一起运行,而不会产生额外的延迟和性能下降(就像“不时”调用 io_service::poll() 的解决方案一样)。
不幸的是,我的解决方案仅适用于 posix 系统,因为它使用 asio::posix::stream_descriptor 。 Windows 支持可能需要完全不同的方法或非常相似的方法 - 我真的不知道。
It's rather old question but for those who are reading it now I would like to share my code which is an implementation of QAbstractEventDispatcher for boost::asio.
All you need is to add the following line before creating QApplication (usually it's in main()).
It will cause, that io_service is being run together with qt application in one thread without additional latency and performance drop (like in solution with calling io_service::poll() "from time to time").
Unfortunately, my solution is for posix systems only, since it use asio::posix::stream_descriptor . Windows support may need completely different approach or quite similar - I don't really know.
如果我正确理解你的问题,那么你已经为 Boost.Asio 编写了代码。 您想在 GUI 应用程序中使用该代码。
您的问题不清楚的是,您是否想通过 asynio 包装 Qt/Gtk 网络层以使代码正常工作,或者您只是在寻找将 gui 事件循环和 asynio 结合在一起的解决方案。
我将假设第二种情况。
Qt 和 Gtk 都有方法将外部事件集成到它们的事件循环中。 请参阅 qtgtk 示例,其中 Qt 事件循环插入到 Gtk 中。
在Qt的具体情况下,如果要为Qt生成事件,可以使用以下类: QAbstractEventDispatcher。
快速浏览一下 boost asio 后,我认为您需要执行以下操作:
If I understand your question correctly, you have code written for Boost.Asio . You would like to use that code inside a GUI application.
What is not clear in your question is if you want to wrap the Qt/Gtk network layers through asynio for your code to work, if you are just looking for a solution for having both a gui event loop and asynio together.
I will assume the second case.
Both Qt and Gtk have methods to integrate foreign events in their event loop. See for example qtgtk where the Qt event loop is plugged into Gtk.
In the specific case of Qt, if you want to generate events for Qt, you can use the following class: QAbstractEventDispatcher.
After a quick look at boost asio, I think you need to do the following:
真正集成主循环是可能的。 这只是一个很大的痛苦(而且我还没有真正尝试过)。
在单独的线程上运行 io_service::run() 可能是正确的方法。
Genuinely integrating the main loops is possible. It's just a big pain (and I have yet to actually try it).
Running io_service::run() on a separate thread is probably the way to go.
我刚刚将 GLib 事件循环集成到 Boost.Asio 中,因此在它从我的记忆中消失之前我将分享一些笔记。
有多种方法可以集成 GLib 和 Boost.Asio。 第一个也是最简单的选择是生成一个新线程来运行
g_main_loop_run()
。 新线程将阻塞此调用,直到您调用g_main_loop_quit()
。 然而,文档仅保证接收GMainContext
的函数的线程安全,因此您最好从 GLib 线程本身调用此函数。 您可以通过调用 g_main_context_invoke() 提交要在 GLib 线程中执行的作业。第二种方法是控制由
g_main_loop_run()
完成的每次迭代。 这可以通过调用 g_main_context_iteration() 来完成。 通过控制轮询,您可以选择不时从任何您想要的线程调用此函数。 例如,如果您正在编写 SDL 游戏,其中轮询确实是合适的答案,那么这是一个不错的选择。如果您依赖投票,就会出现一个悬而未决的问题:我应该多久投票一次? 如果轮询太频繁,则会浪费 CPU,但如果轮询稀疏,则会引入延迟/滞后。 这就是第三个集成选项的用武之地:将
g_main_context_iteration()
调用分解为对g_main_context_prepare()
、g_main_context_query()
的调用序列,g_main_context_check()
和g_main_context_dispatch()
。 但请务必使用g_main_context_new_with_flags(G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING)
创建GMainContext
,否则您将参加 https://gitlab.gnome.org/GNOME/glib/-/commit/e26a8a59813ce651c881fe223e7d1a5034f2f816 。我刚刚编写的集成代码是在线的,您可以在 https://gitlab.com/emilua/glib/-/blob/bc2b4236aa7f296a08739f23522809817229792f/src/service.cpp。 此代码还考虑了 Boost.Asio 链,因此您应该能够从多个线程调用 io_context.run() 并且此代码仍然有效。 如果不需要这个约束,代码可以简化很多。
编写此类代码时需要记住一些技巧:
GLib 特定规则:
GMainContext
是线程安全的。I just integrated GLib event loop into Boost.Asio, so I'll share some notes before it vanishes from my memory.
There are several approaches to integrate GLib and Boost.Asio. First and easiest option is to spawn a new thread to run
g_main_loop_run()
. The new thread will block on this call until you callg_main_loop_quit()
. However the documentation only warranties thread-safety to functions receivingGMainContext
so you better call this function from the GLib thread itself. You can submit jobs to be executed in the GLib thread by callingg_main_context_invoke()
.Second approach is to control each iteration tick done by
g_main_loop_run()
. This can be done by callingg_main_context_iteration()
. By controlling the polling, you have the option to call this function from any thread you want from time to time. This is a good option if you're for instance coding a SDL game where polling really is the appropriate answer.An open question arises if you rely on polling: how frequently should I poll? If you poll too often you waste CPU, but if you poll to sparsely then you introduce delay/lag. That's where the third integration option comes in: break the
g_main_context_iteration()
call into a sequence of calls tog_main_context_prepare()
,g_main_context_query()
,g_main_context_check()
andg_main_context_dispatch()
. But do createGMainContext
withg_main_context_new_with_flags(G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING)
or else you'll run in some races explained at https://gitlab.gnome.org/GNOME/glib/-/commit/e26a8a59813ce651c881fe223e7d1a5034f2f816.The code for the integration I just wrote is online and you can find at https://gitlab.com/emilua/glib/-/blob/bc2b4236aa7f296a08739f23522809817229792f/src/service.cpp. This code also takes Boost.Asio strands into consideration, so you should be able to call
io_context.run()
from multiple threads and this code will still work. If you don't need this constraint, the code can be simplified a lot.There are just a few tricks to keep in mind when you write this kind of code:
And GLib-specific rules:
GMainContext
is thread-safe.GMainContext
but instead directly reference the global one or the thread default one. If this code uses the thread default context, then the solution is simple (just set the thread default before dispatching the handlers). If this code uses the global one, then your application just acquired a new restriction of not creating multipleGMainContext
objects and always refer to the same one as well.