使用 Xlib 处理非事件驱动任务的正确方法是什么?

What is the proper way to handle non-event driven tasks with Xlib?

我正在使用 Xlib 在 C 中为 Linux 编写一个图形程序。问题很简单。 我有一个事件,它发生在 Xlib 事件循环之外,需要处理并且会导致屏幕显示的内容发生一些变化。就我而言,我试图让光标在文本框内闪烁。我的事件循环如下所示:

XEvent event;
while(window.loop_running == 1) {
    XNextEvent(window.dis, &event);
    event_handler(&window, &event); // This is a custom function to handle events
}

解决方案 1a:

我的第一个想法是使用 pthreads 创建第二个循环。第二个循环可以处理异步事件并根据需要绘制到屏幕上。但是,Xlib 不能很好地处理多线程。即使我使用互斥体来阻止 event_handler() 函数,在第二个循环中的异步绘制期间,我仍然会定期从 X 崩溃。此外,如果事件循环没有循环,在大约 10 次调用之后pthread循环,程序锁死。

解决方案 1b:

这可以通过在我的程序开始时调用 XInitThreads() 来解决。但是,这会导致 valgrind 报告内存泄漏。似乎分配了一些内存,但在退出时未释放。如果我调用 XInitThreads(),对 XCloseDisplay() 的调用就会挂起。我仍然没有弄清楚如何在我的程序中销毁和清理 windows ,但是最好将其保存为一个单独的问题。此外,在我的程序开始时调用 XInitThreads() 会阻止程序在从 pthread 循环进行 10 次调用而没有循环事件循环后冻结。但是,在 pthread 循环调用大约 10 次后,对 X 的调用开始阻塞。事件循环循环后,事情会短暂恢复,例如将鼠标悬停在 window 上。但是,当循环事件 activity 停止时,调用会很快再次开始阻塞。有趣的是,我注意到我可以在其他一些程序(例如 Bluefish)中复制这个问题。如果我打开 Bluefish,在主文本框中启动一个光标,然后鼠标移出,大约 10 秒后光标停止闪烁。显然,这并不总是一个问题,因为在一段时间内没有触发 X 事件后,视频播放器的显示会冻结。

解决方案 1c:

我可以通过使用 XSendEvent() 在 pthread 循环完成绘制后循环事件循环来阻止 window 冻结。不过,这似乎真的很哈克。而且我不能保证它会起作用,因为我不知道在什么时候 X 会停止收听。我无法确定此问题的根本原因。正如我所说,它似乎在大约 10 秒后发生,但这取决于我如何更改闪烁光标的循环速率。我很想猜测这是对 X 的实际调用的函数。每次重绘每个像素大约有 2 个。它必须 1) 设置前景色和 2) 将位图缓冲区中的像素绘制到屏幕上。目前,我的 window 仅支持 640x480 的分辨率。当然,我只是猜测,这个可以用来判断故障点,我确实不知道原因。

解决方案二:

我可以放弃所有这些并通过使用 XEventsQueued() 轮询事件队列重新实现事件循环,并在它们出现时进行处理。但老实说,我讨厌这种解决方案。这是一个非常棘手的解决方案,它将增加此应用程序所需的处理能力并增加事件响应延迟,因为我想在轮询之间休眠线程以防止只是旋转线程并挂住 CPU 核心。我正在编写这个程序,目标是快速、稳定和精简程序。

有人有解决办法吗?这是一个如此简单和基本的问题,但我只见过在事件循环中使用 XNextEvent 的示例应用程序。我还没有找到任何关于如何处理事件循环事件之外的示例。谢谢您的帮助。我是 Stack Overflow 的新成员。这是我的第一个post。所以,如果我犯了错误,我深表歉意。

用户:mosvy

写了这条评论:

You should use poll() with the fd obtained with ConnectionNumber() and the fds your other events comes on. When the X11 fd is "ready", you process the events with while(XPending()){ XNextEvent(); ... }. Even then, X11 functions which are of the form request/reply (eg XQueryTree) may stall your event loop. The solution is to switch to xcb (where you could split those in their request/reply parts). IMHO xcb is just as ugly and not much better than Xlib, but it's the only thing readily available.

效果很好!感谢您为我指明正确的方向。我希望你能把它写成答案。我会替你接受的。

编辑: 删除了我之前的编辑,因为后来我发现这个问题是我整个程序中其他地方的错误。事实上,编辑不正确,代码没有正确读取事件。

这是我的事件循环现在的样子。我可能会重新组织它,尝试清理它。但作为概念证明,这里:

// window is a custom struct defined and setup elsewhere in my program
// event_handler() is a custom function elsewhere in my program
window.fd = XConnectionNumber(window.dis);
struct pollfd fds;
fds.fd = window.fd;
fds.events = POLLIN;
int poll_ret;
XEvent event;
while(window.loop_running == 1) {
    poll_ret = poll(&fds, 1, 10);
    if (poll_ret < 0) {
        window.loop_running = 0;
    } else if(poll_ret > 0) {
        while (XPending(window.dis) > 0) {
            XNextEvent(window.dis, &event);
            event_handler(&window, &event);
        }
    }
}

我可以使用 poll() 来监听所有使用内核文件描述符的事件,包括 X 事件。 X 事件的文件描述符是通过 ConnectionNumber() 获得的。我还需要将其设置为侦听 "POLLIN" 类型的文件描述符事件。 Poll 接受一组文件描述符。有关架构的更多信息,请参阅 documentation。当 poll() returns 时,我可以检查它是否超时或事件是否已排队。然后我可以相应地采取行动。

我相信如果需要,这将适用于自定义事件。为此,我可能只需要设置自己的文件描述符即可与之交互。如果需要我会研究一下。

此解决方案意味着我不需要调用 XInitThreads(),因为我不会同时调用 X。