在GTKMM中,在单独的线程中发生无效后,on_draw方法停止调用

时间:2019-01-16 09:46:39

标签: c++ pthreads glib gtkmm

使用GTKMM,我扩展了 DrawingArea 小部件,其思想是外部进程为其提供图像。然后,我的 CameraDrawingArea 将使用 Cairo 以正确的尺寸显示图像。

每次图像到达时,我将其存储并调用invalidate方法,该方法最终会导致对on_draw的调用,在此我可以调整大小并显示图像。

我的问题如下:

  • 按预期显示前10或20张图像。
  • 过一会儿,图像一直来自提供程序进程,我一直打电话给invalidate
  • 但不再调用on_draw

为了在这里展示它,我简化了代码,以便在类外部没有任何内容,并且没有与其他库的链接。我已经用带有for循环的方法替换了提供图像的过程,并通过在小部件区域的中间打印一个简单的文本来显示图像:

  • 在构造函数中,我启动一个新的std::thread以在同一实例中调用doCapture方法。我还设置了字体描述,以备后用。
  • doCapture方法是一个愚蠢的CPU吞噬者,只要refreshDrawing不是keepCapturing,除了不时调用false方法外,它什么都不做。 / li>
  • refreshDrawing通过调用invalidate使整个窗口的矩形无效。
  • Gtk的魔力是调用on_draw并提供一个 Cairo 上下文来绘制任何内容。就我而言,出于测试目的,我绘制了一个褐色居中的整数。
  • 类析构函数通过将keepCapturing设置为false来停止线程,并等待以join终止。
    #include "camera-drawing-area.hpp"

    #include <iostream>

    CameraDrawingArea::CameraDrawingArea():
    captureThread(nullptr) {
        fontDescription.set_family("Monospace");
        fontDescription.set_weight(Pango::WEIGHT_BOLD);
        fontDescription.set_size(30 * Pango::SCALE);

        keepCapturing = true;
        captureThread = new std::thread([this] { 
                doCapture(); 
                });
    }

    void CameraDrawingArea::doCapture() {
        while (keepCapturing) {
            float f = 0.0;
            for (int n = 0; n < 1000; n++) {
                for (int m = 0; m < 1000; m++) {
                    for (int o = 0; o < 500; o++) {
                        f += 1.2;
                    }
                }
            }
            std::cout << "doCapture - " << f << std::endl; 
            refreshDrawing();
        }
    }

    void CameraDrawingArea::refreshDrawing() {
        auto win = get_window();
        if (win) {
            win->invalidate(false);
            std::cout << "refreshDrawing" << std::endl; 
        }
    }

    bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
        std::cout << "on_draw" << std::endl; 

        static char buffer[50];
        static int n = 0;
        sprintf(buffer, "-%d-", n++);

        Gtk::Allocation allocation = get_allocation();
        const int width = allocation.get_width();
        const int height = allocation.get_height();


        auto layout = create_pango_layout(buffer);
        layout->set_font_description(fontDescription);

        int textWidth, textHeight;
        layout->get_pixel_size(textWidth, textHeight);
        cr->set_source_rgb(0.5, 0.2, 0.1);
        cr->move_to((width - textWidth) / 2, (height - textHeight) / 2);
        layout->show_in_cairo_context(cr);
        cr->stroke();

        return true;
    }

    CameraDrawingArea::~CameraDrawingArea() {
        keepCapturing = false;
        captureThread->join();
        free(captureThread);
    }

这是我的头文件:

    #ifndef CAMERA_DRAWING_AREA_HPP
    #define CAMERA_DRAWING_AREA_HPP

    #include <gtkmm.h>
    #include <thread>

    class CameraDrawingArea : public Gtk::DrawingArea {
    public:
        CameraDrawingArea();
        virtual ~CameraDrawingArea();

    protected:
        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

    private:
        bool keepCapturing;
        void doCapture();
        void refreshDrawing();
        std::thread* captureThread;
        Pango::FontDescription fontDescription;
    };
    #endif

问题表现如下:

  • 启动应用程序时,它会忠实显示1、2、3 ...
  • 在第5次和第20次迭代之间(这是随机的,但很少在这些范围之外),它会停止刷新。
  • 由于cout,我可以看到调用了refreshDrawing,请确保也调用了invalidate,但没有调用on_draw

此外,如果我在停止刷新之前就停止了该应用程序,则最终效果会很好。但是,如果我在停止刷新后停止应用程序,则会在下面看到此消息(ID值有所不同):

GLib-CRITICAL **: 10:05:04.716: Source ID 25 was not found when attempting to remove it

我很确定我做错了什么,但是对事情一无所知。任何帮助将不胜感激。

我还检查了以下问题,但它们似乎与我的情况无关:

2 个答案:

答案 0 :(得分:1)

除启动GTK主循环的线程外,不能从任何其他线程使用GTK方法。可能是win->invalidate()调用导致此处出现问题。

相反,使用Glib::Dispatcher与主线程通信,或者使用gdk_threads_add_idle()获得更C风格的解决方案。

答案 1 :(得分:0)

基于答案形式@ptomato,我重写了示例代码。黄金法则是不要从另一个线程调用GUI函数,但是如果这样做,则先获取一些特定的GDK锁。这就是Glib::Dispatcher的目的:

  

如果在主GUI线程(因此将成为接收器线程)中构造了Glib :: Dispatcher对象,则任何工作线程都可以在其上发射并让连接的插槽安全地执行gtkmm函数。

基于此,我添加了一个新的私有成员Glib::Dispatcher refreshDrawingDispatcher,该成员将允许线程安全地invalidate Windows区域:

    #ifndef CAMERA_DRAWING_AREA_HPP
    #define CAMERA_DRAWING_AREA_HPP

    #include <gtkmm.h>
    #include <thread>

    class CameraDrawingArea :
    public Gtk::DrawingArea {
    public:
        CameraDrawingArea();
        virtual ~CameraDrawingArea();

    protected:
        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

    private:
        bool keepCapturing;
        void doCapture();
        void refreshDrawing();
        Glib::Dispatcher refreshDrawingDispatcher;
        std::thread* captureThread;
        Pango::FontDescription fontDescription;
    };
    #endif

然后,我已将调度程序连接到refreshDrawing方法。我在类构造函数中执行此操作,该类构造函数在GUI启动期间被调用,因此在主GUI线程中被调用:

    CameraDrawingArea::CameraDrawingArea():
    refreshDrawingDispatcher(),
    captureThread(nullptr) {
        fontDescription.set_family("Monospace");
        fontDescription.set_weight(Pango::WEIGHT_BOLD);
        fontDescription.set_size(30 * Pango::SCALE);

        keepCapturing = true;
        captureThread = new std::thread([this] {
                doCapture(); 
                });

        refreshDrawingDispatcher.connect(sigc::mem_fun(*this, &CameraDrawingArea::refreshDrawing));
    }   

最后,线程必须调用调度程序:

    void CameraDrawingArea::doCapture() {
        while (keepCapturing) {
            float f = 0.0;
            for (int n = 0; n < 1000; n++) {
                for (int m = 0; m < 1000; m++) {
                    for (int o = 0; o < 500; o++) {
                        f += 1.2;
                    }
                }
            }
            std::cout << "doCapture - " << f << std::endl; 
            refreshDrawingDispatcher.emit();
        }
    }

现在,它可以正常工作了。