如何将Boost.Asio主循环集成到Qt4或GTK等GUI框架中

发布于 2024-07-24 21:41:32 字数 891 浏览 6 评论 0原文

有没有办法将 Boost.Asio 与 Qt4(首选)或 GTK 主循环集成? GTK 提供类似 poll(2) 的 API,所以技术上应该是可行的。 Qt 提供了自己的网络层,但我更喜欢使用为 Boost.Asio 编写的现有代码。 我想使用额外的线程来集成它们。

有没有关于如何为 Qt4(首选)或 GTKmm 执行此操作的参考?

谢谢。

编辑

我想澄清一些事情以使答案更容易。 Qt 和 GTKmm 都提供 “选择类似”功能:

所以,问题是,如何将现有的“选择器/轮询器”作为反应器集成到 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:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

人疚 2024-07-31 21:41:32

简单:
构建一个调用属于 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's tick 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 call io_service::run() as it is blocking and could hang the gui. Instead use io_service::poll() or io_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 call poll_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.

伤痕我心 2024-07-31 21:41:32

这是一个相当老的问题,但对于那些现在正在阅读它的人,我想分享我的代码,它是 QAbstractEventDispatcher 的实现,用于提升: :亚洲。

您所需要的只是在创建 QApplication 之前添加以下行(通常在 main() 中)。

QApplication::setEventDispatcher(new QAsioEventDispatcher(my_io_service));

这将导致 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()).

QApplication::setEventDispatcher(new QAsioEventDispatcher(my_io_service));

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.

在你怀里撒娇 2024-07-31 21:41:32

如果我正确理解你的问题,那么你已经为 Boost.Asio 编写了代码。 您想在 GUI 应用程序中使用该代码。

您的问题不清楚的是,您是否想通过 asynio 包装 Qt/Gtk 网络层以使代码正常工作,或者您只是在寻找将 gui 事件循环和 asynio 结合在一起的解决方案。

我将假设第二种情况。

Qt 和 Gtk 都有方法将外部事件集成到它们的事件循环中。 请参阅 qtgtk 示例,其中 Qt 事件循环插入到 Gtk 中。

在Qt的具体情况下,如果要为Qt生成事件,可以使用以下类: QAbstractEventDispatcher

快速浏览一下 boost asio 后,我认为您需要执行以下操作:

  • 有一个持续时间为零的循环 QTimer,它始终调用 io_service::run() 。 这样,一旦您的异步操作完成,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:

  • have a recurring QTimer with duration zero that calls io_service::run() all the time. That way, boost::asio will call your completion handler as soon as your asynchronous operation is completed.
  • in your completion handler, two options:
    • if your completion operation is a long one, separated from the GUI, do your business and make sure to call qApp.processEvents() regularly to keep the GUI responsive
    • if you just want to communicate back with the gui:
      1. define a custom QEvent type
      2. subscribe to this event
      3. post your event to Qt event loop using QCoreApplication::postEvent().
梦萦几度 2024-07-31 21:41:32

真正集成主循环是可能的。 这只是一个很大的痛苦(而且我还没有真正尝试过)。

在单独的线程上运行 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.

半衬遮猫 2024-07-31 21:41:32

我刚刚将 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() 并且此代码仍然有效。 如果不需要这个约束,代码可以简化很多。

编写此类代码时需要记住一些技巧:

  • 注意堆栈溢出。 处理程序是否可以触发对另一个就绪源的检测,而该源又会以嵌套方式调用第二个处理程序? 避免这种情况。
  • 调度语义并不总是安全的。 编写代码时很少会假设调用函数来调度某些 IO 时可以执行任意处理程序。 默认情况下只需执行发布语义,最多提供调度语义作为选项。
  • 小心饥饿和不公平。 始终时不时地将“事件循环滴答”交给其他服务。

GLib 特定规则:

  • GMainContext 是线程安全的。
  • 使用 GLib 的代码可能不会对 GMainContext 进行显式引用/参数,而是直接引用全局引用/参数或线程默认引用/参数。 如果此代码使用线程默认上下文,则解决方案很简单(只需在分派处理程序之前设置线程默认值)。 如果此代码使用全局对象,那么您的应用程序将获得一项新的限制,即不能创建多个 GMainContext 对象,并且始终引用同一个对象。
  • GLib 没有待处理工作的概念。 它期望它的循环永远运行。 我之前链接的代码有一个解决方案。
  • 不要重复使用之前的tick迭代中的“fd watchers”来尝试保存系统调用。 兴趣集不是有状态的,GLib 不需要通知您它已经关闭了上一次迭代中使用的 fd。 如果该事件发生,程序中的另一个线程可能会打开具有相同编号的 fd,并且您的 fd 观察程序将成为定时炸弹。 危险程度取决于“fd watcher”的实现(例如我使用了 epoll)和特定于您的程序的交互。

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 call g_main_loop_quit(). However the documentation only warranties thread-safety to functions receiving GMainContext so you better call this function from the GLib thread itself. You can submit jobs to be executed in the GLib thread by calling g_main_context_invoke().

Second approach is to control each iteration tick done by g_main_loop_run(). This can be done by calling g_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 to g_main_context_prepare(), g_main_context_query(), g_main_context_check() and g_main_context_dispatch(). But do create GMainContext with g_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:

  • Pay attention to stack overflows. Is it possible for a handler to trigger detection of another ready source that will in turn call the second handler in a nesting fashion? Avoid that.
  • Dispatch semantics are not always safe. Code is rarely written under the assumption that arbitrary handlers can be executed when it calls a function to schedule some IO. Just do post semantics by default and at most offer dispatch semantics as an option.
  • Be careful of starvation and unfairness. Always yield the “event loop tick” to other services now and then.

And GLib-specific rules:

  • GMainContext is thread-safe.
  • Code using GLib might not take an explicit reference/parameter to a 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 multiple GMainContext objects and always refer to the same one as well.
  • GLib has no concept of pending work. It expects its loop to be running forever. The code I linked earlier has a solution for that.
  • Do not reuse “fd watchers” from previous tick iterations in an attempt to save syscalls. The interest set is not stateful and GLib doesn't need to inform you it has closed a fd that was used in the previous iteration. If that event happens, another thread from the program could open a fd that has the same number and your fd watcher just became a time bomb. The amount of danger will depend on the “fd watcher” implementation (I used epoll for instance) and interactions specific to your program.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文