繁体   English   中英

GFileMonitor-g_signal_handler_block“已更改”信号不会阻止处理程序吗?

[英]GFileMonitor - g_signal_handler_block “changed” signal doesn't block handler?

所有,

可能要花一点时间进行设置。 在过去的几个月中,我有一个小型的编辑器项目[1]。 我最初想在当前的编辑器文件上实现一个inotify监视,以防止被前面的过程修改。 我创建的自定义信号,并且例程addremove ,和monitor (与pselect )手表和例程来blockunblock自定义信号的发射,以允许正常save/save as不触发回调。 我遇到的问题是如何将monitor添加到gtk_event_loop以便对循环的每次迭代进行检查。 我决定使用g_idle_add并转到gtk-app-devel列表来确定该方法是否合理或是否有更好的方法。 共识是使用GIO / GFileMonitor而不是直接使用inotify

快速解决当前问题。 我重写了使用GFileMonitor/g_file_monitor_file的实现,并重写了blockunblock例程以阻止对"changed"信号的处理,从而允许正常save/save as而无需触发回调。 问题是当我在保存文件之前block实例和handler_id的回调时,该回调仍会触发。 当将inotify实现与自定义信号一起使用时,阻止信号的发射效果很好。 我已将其发布回gtk-app-devel列表,但没有得到任何回报-这就是为什么我在这里询问。 为什么g_signal_handler_block与GIO / GFileMonitor不是块处理的"changed"信号的回调? (更重要的是,我该如何解决)

注意: (MCVE-完整的测试代码位于https://github.com/drankinatty/gtktest )。 要使用GtkSourceView2进行构建,只需键入make with=-DWGTKSOURCEVIEW2 ,它将构建为bin/gtkwrite ;否则,不进行构建,只需键入make ,它将构建为bin/gtkedit

相关代码的逻辑如下( app是包含相关编辑器变量/信息和设置的struct的实例)GIO / GFileMonitor实现位于gtk_filemon.[ch] ,保存功能的包装位于gtk_filebuf.c

typedef struct {
    ...
    gchar           *filename;
    GFileMonitor    *filemon;
    gulong          mfp_handler;
    ...
} kwinst;

kwinst *app = g_slice_new (kwinst);

我将手表设置为:

GFile *gfile = g_file_new_for_path (app->filename);
...

/* create monitor for app->filename */
app->filemon = g_file_monitor_file (gfile,
                        G_FILE_MONITOR_NONE,
                        cancellable, &err);
...

/* connect changed signal to monitored file saving ID */
app->mfp_handler = g_signal_connect (G_OBJECT(app->filemon), "changed",
                        G_CALLBACK (file_monitor_on_changed), data);

实例( app->filemon )和handler_id( app->mfp_handler )均被保存。 mfp只是外来进程修改的缩写)为了防止在正常的保存/保存操作过程中处理更改,我创建了阻止和取消阻止功能以防止对文件的更改触发回调,例如,下面用debug g_print进行显示要求:

void file_monitor_block_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_print ("blocking changed (%lu)\n", app->mfp_handler);

    g_signal_handler_block (app->filemon, app->mfp_handler);
}

void file_monitor_unblock_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_print ("unblocking changed (%lu)\n", app->mfp_handler);

    g_signal_handler_unblock (app->filemon, app->mfp_handler);
}

为了实现block/unblock ,我将文件'save / save as'函数包装在block ,然后是save [2],然后是unblock ,但是回调仍在正常保存时触发。 例如,我拥有的保存功能的相关部分是:

if (app->mfp_handler)                   /* current file monitor on file */
    file_monitor_block_changed (app);   /* block "changed" signal */

g_print ("  buffer_write_file (app, filename)\n");
buffer_write_file (app, filename);      /* write to file app->filename */

if (filename)
    file_monitor_add (app);             /* setup monitoring on new name */
else if (app->mfp_handler)
    file_monitor_unblock_changed (app); /* unblock "changed" signal */

使用上面的debug g_print语句,在以下输出中发出save结果:

$ ./bin/gtkwrite
blocking changed (669)
  buffer_write_file (app, filename)
unblocking changed (669)
Monitor Event: File = /home/david/tmp/foo.txt.UY9IXY
G_FILE_MONITOR_EVENT_DELETED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CREATED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED

我是否包含block/unblock都没有区别,回调的触发是不变的。 我怀疑问题在于, "changed"信号及其处理是通过GIO而不是GTK来实现的(就像inotify实现的自定义信号一样),并且在阻挠处理"changed" g_signal_handler_block使用"changed"信号-尽管我在文档中找不到区别。 该文档指出:

 To get informed about changes to the file or directory you are monitoring, connect to the “changed” signal. The signal will be emitted in the thread-default main context of the thread that the monitor was created in (though if the global default main context is blocked, this may cause notifications to be blocked even if the thread-default context is still running). 

https://developer.gnome.org/gio/stable/GFile.html#g-file-monitor-file

应用程序本身不使用任何显式线程,并且不fork保存的任何部分。 因此,我对如何block/unblock不阻止处理"changed"信号感到block/unblock 保存完全包装在block/unblock ,除非g_file_set_contents调用是异步的,否则我不会看到任何时序问题。

为什么调用g_signal_handler_block/g_signal_handler_unblock无法阻止处理对当前文件的更改发出的"changed"信号? 我可以g_signal_handler_disconnect并且什么也不会触发,但我不必disconnect即可暂时阻止处理。 我想念什么?

为了完整file_monitor_on_changed函数与脚注一起包含在下面:

void file_monitor_on_changed (GFileMonitor *mon, 
                                GFile *file, GFile *other,
                                GFileMonitorEvent evtype,
                                gpointer data)
{
    kwinst *app = (kwinst *)data;

    g_print ("Monitor Event: File = %s\n", g_file_get_parse_name (file));

    switch (evtype)
    {
        case G_FILE_MONITOR_EVENT_CHANGED:
            /* prompt or emit custom signal modified by foreign process */
            g_print ("G_FILE_MONITOR_EVENT_CHANGED\n");
            break;
        case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
            g_print ("G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT\n");
            break;
        case G_FILE_MONITOR_EVENT_DELETED:
            /* avoid firing on normal '.tmp' file delete */
            if (g_strcmp0 (g_file_get_parse_name (file), app->filename)) {
                g_print ("  ignoring 'tmp' file delete.\n");
                break;
            }
            /* prompt or emit custom signal modified by foreign process */
            g_print ("G_FILE_MONITOR_EVENT_DELETED\n");
            /* prompt save file */
            break;
        case G_FILE_MONITOR_EVENT_CREATED:
            g_print ("G_FILE_MONITOR_EVENT_CREATED\n");
            break;
        case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
            g_print ("G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED\n");
            break;
        case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
            g_print ("G_FILE_MONITOR_EVENT_PRE_UNMOUNT\n");
            break;
        case G_FILE_MONITOR_EVENT_UNMOUNTED:
            g_print ("G_FILE_MONITOR_EVENT_UNMOUNTED\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED:
            g_print ("G_FILE_MONITOR_EVENT_MOVED\n");
            /* prompt save file */
            break;
        case G_FILE_MONITOR_EVENT_RENAMED:
            /* prompt save file */
            g_print ("G_FILE_MONITOR_EVENT_RENAMED\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED_IN:
            g_print ("G_FILE_MONITOR_EVENT_MOVED_IN\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED_OUT:
            g_print ("G_FILE_MONITOR_EVENT_MOVED_OUT\n");
            break;
        default:
            g_print ("unknown EVENT on changed signal.\n");
    }

    if (mon || other) {}
}

在其他情况下,使用g_signal_handler_block可以正常工作

作为评论中提到的一点,我可以确认我可以轻松地blockunblock其他信号处理程序而没有问题。 具体来说,在使用inotify实现时,我创建了以下自定义信号:

/* create signal to monitor file modified by foreign process */
app->SIG_MODFP = g_signal_new ("modified-foreign",
                                GTK_TYPE_TEXT_BUFFER,
                                GTK_RUN_ACTION,
                                0,
                                NULL,
                                NULL,
                                NULL,
                                G_TYPE_NONE,
                                1,
                                G_TYPE_POINTER);

然后将信号连接到处理程序,并按以下方式保存handler_id:

/* custom signals */
app->mfp_handler2 = g_signal_connect (GTK_TEXT_BUFFER(app->buffer), 
                  "modified-foreign",
                  G_CALLBACK (on_modified_foreign), app);

on_modified_foreign回调是用于测试阻塞/解除阻塞的简单测试回调:

void on_modified_foreign (GtkTextBuffer *buffer,
                        kwinst *app)
{
    dlg_info ("File has changed on disk, reload?", "Modified by Foreign Process");

    if (buffer || app) {}
}

上述dlg_info仅仅是一个包装gtk_message_dialog_new时,弹出一个对话框"modified-foreign"信号发出。

然后执行一个简单的测试,其中一个菜单项导致信号被发出,例如:

void menu_status_bigredbtn_activate (GtkMenuItem *menuitem, kwinst *app)
{
    g_signal_emit_by_name (G_OBJECT(app->buffer), "modified-foreign::", app);
}

而且,最终阻止/取消阻止就可以了:

void menu_status_block_activate (GtkMenuItem *menuitem, kwinst *app)
{
    if (!app->mfp_handler2) return;
    GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
    g_signal_handler_block (buffer, app->mfp_handler2);
}

void menu_status_unblock_activate (GtkMenuItem *menuitem, kwinst *app)
{
    if (!app->mfp_handler2) return;
    GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
    g_signal_handler_unblock (buffer, app->mfp_handler2);
}

一切正常。 选择bigredbtn菜单项,信号被发射并向上弹出对话框。 然后选择block菜单项,然后再次尝试bigredbtn什么也没有发生,没有对话框,什么也没有。 然后选择unblock菜单项,然后再次选择bigredbtn ,每次选择时都会再次弹出对话框。 (并且在处理程序被阻塞时发出的信号没有排队,并且一旦被解除阻塞,就不会调用该处理程序)

这就是我遇到的问题。 问题的很大一部分是没有选择GIO源代码,尽管它在很大程度上是一个很大的黑匣子。 一切在GTK方面都可以正常工作,但是当执行涉及GIO功能的相同操作时,结果似乎无法达到预期效果。

感谢您对这个问题的其他见解。

脚注1: https //github.com/drankinatty/gtkwrite

脚注2: buffer_write_file调用g_file_set_contents写入磁盘。

好的,我已经玩了很长时间,并且找到了解决方案。 出于某种原因,关键似乎是出于GIO大计划中的原因,从原始连接信号的同一源中调用blockunblock 。例如,在gtk_filemon.c源和然后从任何地方调用(如上述测试菜单项中所述)都可以正常工作,例如:

void file_monitor_block_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_signal_handler_block (app->filemon, app->mfp_handler);
}

void file_monitor_unblock_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_signal_handler_unblock (app->filemon, app->mfp_handler);
}

这使来自menu_status_block_activatemenu_status_unblock_activate上述两个函数的调用可以按预期工作,并在阻止时成功阻止对"changed"信号的处理,并在取消阻止时恢复。 为什么g_signal_handler_unblock使用实例handler_id直接调用g_signal_handler_blockg_signal_handler_unblockg_signal_handler_blockg_signal_handler_unblock将只剩下文档之谜。

注意:如果有人想玩,我将把github.com gtktest代码保留到4月中旬,之后工作代码将存在于https://github.com/drankinatty/gtkwrite

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM