【问题标题】:GTK+3 GUI freezes randomly (after 1 hour or after 20 minutes)GTK+3 GUI 随机冻结(1 小时后或 20 分钟后)
【发布时间】:2018-05-06 15:44:39
【问题描述】:

我正在用 C 语言开发一个程序,它使用 GTK+3 并遵循 - 或多或少 - MVC 架构:

  • 模型每 20 毫秒通过调用 model_update 更新一次(它不调用 GTK 函数);
  • GUI 通过读取模型变量每 50 毫秒调用一次 gui_update 进行更新。

我的问题是 GUI 在随机运行时间后冻结,可能是 20 分钟或超过 1 小时,我不知道为什么。也许我应该知道一些关于 GTK 的事情?

注意: 使用互斥锁可以保护对模型变量的访问。

非常感谢您的帮助!!

#include <signal.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/prctl.h> 

void *
thread_gui(void* data)
{ 
    g_timeout_add(50, handler_timer_gui_update, NULL); // updates the GUI each 50ms
    gtk_main();
    pthread_exit(NULL);
}

gint
handler_timer_gui_update(gpointer data)
{
    gui_update();
    // gui_update reads the model and updates GUI by using
    // gtk_label_set_text, gtk_spin_button_set_value, cairo_paint
    return TRUE;
}

void
launch_periodical_call_updating_model( )
{
    signal( SIGRTMIN + 1, model_update );

    timer_t timer;
    struct sigevent event;
    event.sigev_notify = SIGEV_SIGNAL;
    event.sigev_signo = SIGRTMIN + 1;
    event.sigev_value.sival_ptr = &timer;
    timer_create(CLOCK_REALTIME, &event, &timer);

    struct itimerspec spec;
    spec.it_value.tv_nsec = 20 * 1000000; // updates the model each 20 ms
    spec.it_value.tv_sec = 0;
    spec.it_interval = spec.it_value;

    timer_settime( timerModel, 0, &spec, NULL);
}

int
main( int argc, char *argv[] )
{ 
    pthread_t pthread_gui;

    init_model( ); // init model variables
    launch_periodical_call_updating_model( ); 

    // signal capture to exit
    signal( SIGINT, ctrlc_handler );
    signal( SIGTERM, ctrlc_handler );

    // GUI
    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();
    gtk_init( &argc, &argv );
    create_gui ( ); // building GTK Window with widgets  
    pthread_create(&pthread_gui, NULL, thread_gui, NULL);
    gdk_threads_leave();

    // Leaving the program
    pthread_join( pthread_gui, NULL ); 
    stop_model( ); //It stops to update the model and releases memory

    return 0;
}

void
ctrlc_handler( int sig )
{
    gtk_main_quit(); 
} 

【问题讨论】:

  • 尝试在信号处理程序中获取互斥锁是一个非常糟糕的主意:(

标签: c gtk gtk3


【解决方案1】:

您不应该在 GTK 中使用 Unix 信号。见signal-safety(7)signal(7)

您应该考虑只使用 Glib 计时器(而不是使用任何 POSIX 计时器)。阅读Glib event loop(用于GTK)并使用g_timeout_add或相关函数。

如果您坚持要在 GTK 程序中处理 Unix 信号,请对它们执行Qt recommends 的处理:在初始化时为 self 设置一个 pipe(7),并轮询该文件描述符(在 GTK 中,使用 g_source_add_unix_fdg_io_channel_unix_new ) 您的 Unix 信号处理程序将 write(2) (一次一个或很少几个字节)。 BTW,Linux 也提供signalfd(2)

最后,GTK 并不是真正的线程友好的(另请参阅this),所有 GTK 函数都应该(仅)从您的主线程(而不是其他一些 pthread_gui)调用。实际上,如果您确实需要使用多个线程进行编码,请考虑让您的非 GUI 线程也通过管道与 GTK 进行通信和同步,并使用Glib thread functions 来启动和管理它们。

您甚至可以考虑一些多处理方法:让 GUI 程序在一个进程中,而另一个处理(多线程)在另一个进程中(可能由 GUI 程序使用 g_spawn_async_with_pipes 启动)。

了解continuationscontinuation-passing style。它可能会帮助您从这些方面进行思考。

【讨论】:

  • 现在完美运行!!我使用 g_timeout_add 而不是信号函数...性能还不错 =) “continuations”乍一看很奇怪。
【解决方案2】:

您应该停止使用信号、线程,而只使用 GTK+ 主循环的事件源。因此,如果您想每 50 毫秒调用一次代码,那么如果您需要每秒刷新一次以上 UI,只需使用 g_timeoutg_timeout_full(否则 g_idle_addg_timeout_add_seconds 是更好的选择)。

但是,我认为每 20 毫秒更新一次模型并每 50 毫秒显示一次结果没有意义。您只是在浪费计算能力,因为您计算了不会显示的东西。更新模型不应该太昂贵,因为您可以在不到 20 毫秒的时间内完成。因此,如果您不关心确切的时间,只需在连接到 g_idle_add 的回调中更新模型,并在回调结束时,通过在小部件上调用 gtk_widget_queue_drawgtk_widget_queue_draw_area 来要求更新 UI需要重新绘制。然后它将尽可能快地计算(因此可以适应每台计算机的性能),并且不会计算未显示的内容。然后使用计时器确定两次刷新之间经过的时间。如果太高,可以使用g_timeout_add而不是g_idle_add来限制刷新率,使用更长的刷新周期。

【讨论】:

  • 我现在明白为什么我从信号函数中调用 gtk_label_set_text 或 gtk_spin_button_set_value 会遇到一些麻烦...... gtk_widget_queue_draw 似乎是更新 cairo 表面的好方法,我要检查一下!
  • 我确认嘿嘿它有效!...而且它比定期调用更好 cairo = gdk_cairo_create(gtk_widget_get_window(drawing_area));绘图功能(开罗); cairo_destroy(cairo);
  • 是的,如果您使用的是 GTK 3,您应该在连接到“draw”信号的回调中进行绘图。你被传递了一个cairo_t *,它是那里的开罗上下文,所以不需要自己创建或销毁开罗上下文。请参阅文档的“自定义绘图”部分:developer.gnome.org/gtk3/stable/ch01s05.html
猜你喜欢
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 1970-01-01
  • 2022-09-24
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
  • 2022-08-03
相关资源
最近更新 更多