В GTKMM метод on_draw перестает вызываться после того, как в отдельном потоке происходит недействительность - PullRequest
0 голосов
/ 16 января 2019

Используя GTKMM, я расширяю виджет DrawingArea , полагая, что внешний процесс предоставляет ему изображения. Мой CameraDrawingArea отобразит изображения в нужном размере, используя Cairo .

Каждый раз, когда изображение поступает, я сохраняю его и вызываю метод invalidate, который в конечном итоге заканчивается вызовом on_draw, где я могу изменить размер и отобразить изображение.

Моя проблема заключается в следующем:

  • Первые 10 или 20 изображений отображаются так, как я ожидал.
  • Через некоторое время изображения продолжают поступать из процесса провайдера, я продолжаю звонить invalidate
  • но on_draw больше не вызывается.

Чтобы показать это здесь, я упростил код, чтобы не было ничего внешнего для класса и никакой связи с другими библиотеками. Я заменил процесс предоставления изображений методом for-loop и отображением изображения, напечатав простой текст в середине области виджета:

  • В конструкторе я запускаю новый std::thread для вызова метода doCapture в том же экземпляре. Я также настроил описание шрифта, чтобы использовать его позже.
  • Метод doCapture является глупым пожирателем ЦП, который ничего не делает, кроме время от времени вызова метода refreshDrawing, если keepCapturing не равен false.
  • refreshDrawing делает недействительным прямоугольник всего окна посредством вызова invalidate.
  • Магия Gtk предполагает вызов on_draw и предоставление Каирского контекста для рисования чего угодно. В моем случае для целей тестирования я рисую коричневое центрированное целое число.
  • Деструктор класса останавливает поток, устанавливая для 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 нет.

Кроме того, если я остановлю приложение до того, как оно перестанет обновляться, то оно закончится красиво. Но если я остановлю приложение после того, как оно перестанет обновляться, я увижу это сообщение ниже (значение идентификатора меняется):

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

Я совершенно уверен, что я делаю что-то не так, но не знаю, что. Любая помощь будет оценена.

Я также проверил следующие вопросы, но они, похоже, не связаны с моим делом:

Ответы [ 2 ]

0 голосов
/ 26 января 2019

Исходя из формы ответа @ptomato, я переписал пример кода. Золотое правило - - не вызывайте функции графического интерфейса из другого потока, но если вы это сделаете, то сначала получите некоторые определенные блокировки GDK . Это цель Glib :: Dispatcher :

Если объект Glib :: Dispatcher создается в основном потоке графического интерфейса пользователя (который, следовательно, будет потоком-получателем), любой рабочий поток может генерировать в нем поток, и подключенные слоты могут безопасно выполнять функции gtkmm.

Исходя из этого, я добавил новый закрытый элемент Glib::Dispatcher refreshDrawingDispatcher, который позволит потокам безопасно invalidate область окна:

    #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();
        }
    }

И теперь, это работает без дальнейших проблем.

0 голосов
/ 19 января 2019

Вы не можете использовать методы GTK из любого другого потока, кроме того, в котором вы начали основной цикл GTK. Возможно, вызов win->invalidate() приводит к тому, что все идет не так, как надо.

Вместо этого используйте Glib::Dispatcher для связи с основным потоком или используйте gdk_threads_add_idle() для более решения в стиле C.

...