Решено: я получаю сообщение об ошибке при попытке использовать потоки с GTK и Pycairo (чтобы нарисовать окно и передать сигнал из другого потока) - PullRequest
0 голосов
/ 22 марта 2020

SOLUTION

  • Удалить канал и связанный с ним код
  • Добавить новую функцию обновления внутри класса окна, которая принимает новые фигуры в качестве параметра
  • Изменить инициализация класса
  • вызов функции обновления

Модификации для решения

Извинения, но уценка diff не отображается должным образом, надеюсь, вы должно все же получить представление о том, как работает решение

Класс окна

class Window(Gtk.Window):
-    __gsignals__ = {
-        'update_signal': (GObject.SIGNAL_RUN_FIRST, None,
-                      ())
-    }
-
-    def do_update_signal(self):
-        print("UPDATE SIGNAL CALLED")
-        self.shapes = self.shapes_channel.read()
-        print("Num new shapes:", len(self.shapes))
-        self.show_all()

в методе класса init_ui

         self.connect("delete-event", Gtk.main_quit)
+        self.show_all()
+        a = self.darea.get_allocation()
+        print (a.x, a.y, a.width, a.height)
+        self.img = cairo.ImageSurface(cairo.Format.RGB24, a.width, a.height)

новый класс method update_shapes

+    def update_shapes(self, shapes):
+        self.shapes = shapes
+        cr = cairo.Context(self.img)
+        self.draw_background(cr)
+        for shape in self.shapes:
+            shape.draw(cr)
+        self.darea.queue_draw()
+        return True

Основной код

- shapes_channel = Channel()
  iter_num = 0
- def optimize(chan, prob, signaller):
+ def optimize(prob, signaller):
      def print_iter_num(xk):
          global iter_num
          iter_num += 1
          prob.update_positions(xk)
          prob.update_grads(jacobian(xk))
          new_shapes = convert_grid(prob.grid, building_size=1.0/GRID_SIZE)
-         chan.write(new_shapes)
-         signaller.emit("update_signal")
+         GLib.idle_add(signaller.update_shapes, new_shapes)
          print("Iteration", iter_num, "complete...")
      try:
          sol = minimize(objective, x0, bounds = all_bounds, constraints=constraints, options={'maxiter': MAX_ITER, 'disp': True}, callback=print_iter_num, jac=jacobian)
          prob.update_positions(sol.x)
      except Exception as e:
          print("ran into an error", e)

- window = new_window(shapes_channel=shapes_channel)
+ window = new_window()
- x = threading.Thread(target=optimize, args=(shapes_channel, optim_problem, window))
+ x = threading.Thread(target=optimize, args=(optim_problem, window))
  x.start()
  window.run()

ВОПРОС

Класс окна

import cairo
import gi
import math
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject

class Line():
    def __init__(self, start, end, thickness, colour):
        self.start = start
        self.end = end
        self.thickness = thickness
        self.colour = colour

    def draw(self, cr):
        cr.move_to(*self.start)
        cr.line_to(*self.end)
        cr.set_source_rgba(*self.colour)
        cr.set_line_width(self.thickness)
        cr.stroke()

class Polygon():
    def __init__(self, points, line_colour, line_thickness, fill_colour=None):
        self.points = points # points should be an iterable of points
        self.line_colour = line_colour
        self.line_thickness = line_thickness
        self.fill_colour = fill_colour

    def draw(self, cr):
        cr.move_to(*self.points[0])
        for point in self.points[1:]:
            cr.line_to(*point)
        cr.close_path()
        cr.set_source_rgba(*self.line_colour)
        cr.set_line_width(self.line_thickness)
        cr.stroke()
        if self.fill_colour is not None:
            cr.move_to(*self.points[0])
            for point in self.points[1:]:
                cr.line_to(*point)
            cr.close_path()
            cr.set_source_rgba(*self.fill_colour)
            cr.fill()

class Window(Gtk.Window):
    __gsignals__ = {
        'update_signal': (GObject.SIGNAL_RUN_FIRST, None,
                      ())
    }

    def do_update_signal(self):
        print("UPDATE SIGNAL CALLED")
        self.shapes = self.shapes_channel.read()
        print("Num new shapes:", len(self.shapes))
        self.show_all()

    def __init__(self, shapes_channel, window_size, background_colour=(1, 1, 1, 1), title="GTK window"):
        super(Window, self).__init__()
        self.width = window_size[0]
        self.height = window_size[1]
        self.background_colour = background_colour
        self.title = title
        self.shapes = []
        self.shapes_channel = shapes_channel
        self.init_ui()

    def init_ui(self):    
        darea = Gtk.DrawingArea()
        darea.connect("draw", self.on_draw)
        self.add(darea)
        self.set_title(self.title)
        self.resize(self.width, self.height)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.connect("delete-event", Gtk.main_quit)

    def draw_background(self, cr: cairo.Context):
        cr.scale(self.width, self.height)
        cr.rectangle(0, 0, 1, 1)  # Rectangle(x0, y0, x1, y1)
        cr.set_source_rgba(*self.background_colour)
        cr.fill()

    def on_draw(self, wid, cr: cairo.Context):
        self.draw_background(cr)
        for shape in self.shapes:
            shape.draw(cr)

    def run(self):
        Gtk.main()

def new_window(shapes_channel,
         window_size=(1000, 1000),
         background_colour=(1,1,1,1),
         title="3yp"):
    return Window(shapes_channel,
                    window_size=window_size,
                    background_colour=background_colour,
                    title=title)

Я пытаюсь запустить окно, которое может рисовать определенные мной формы (линии и многоугольники).

Раньше он работал нормально, когда я предоставлял ему список форм и запускал его в конце моего приложения

Однако я пытаюсь добавить интерактивность и перерисовать список фигур. когда вызывается update_signal и передается список новых фигур по shape_channel, который является частью конструктора.

Основной код

Вот соответствующие биты из моего основного кода:

shapes_channel = Channel()
iter_num = 0

def optimize(chan, prob, signaller):
    def print_iter_num(xk):
        global iter_num
        iter_num += 1
        prob.update_positions(xk)
        prob.update_grads(jacobian(xk))
        new_shapes = convert_grid(prob.grid, building_size=1.0/GRID_SIZE)
        chan.write(new_shapes)
        signaller.emit("update_signal")
        print("Iteration", iter_num, "complete...")
    try:
        sol = minimize(objective, x0, bounds = all_bounds, constraints=constraints, options={'maxiter': MAX_ITER, 'disp': True}, callback=print_iter_num, jac=jacobian)
        prob.update_positions(sol.x)
    except Exception as e:
        print("ran into an error", e)

window = new_window(shapes_channel=shapes_channel)
x = threading.Thread(target=optimize, args=(shapes_channel, optim_problem, window))
x.start()
window.run()

Как вы видите:

  1. Создан объект Channel () с именем shape_channel
  2. Создается новое окно, с которого shape_channel передается в конструктор через промежуточная функция new_window.
  3. Это окно передается другому потоку, так что другой поток может излучать соответствующий сигнал ("update_signal")
  4. Другой поток запускается
  5. Окно запускается в главном потоке. Я получаю следующий вывод консоли:
UPDATE SIGNAL CALLED
Num new shapes: 31
Gdk-Message: 01:27:14.090: main.py: Fatal IO error 0 (Success) on X server :0.

Из вывода консоли мы можем сделать вывод, что сигнал вызывается успешно, и новые формы передаются в окно и хранится правильно, но в строке он не работает self.show_all().

Это объект, который ранее работал нормально, и производит графический вывод, и я могу Мы можем думать только о двух возможных вещах, которые могли измениться с точки зрения объектов:

  1. Объект Channel работает так, как задумано, но, возможно, простое присутствие объекта, разделяемого между потоками, бросает все это в disarray
  2. Несмотря на то, что он находится в главном потоке, ему не нравится, что есть другие потоки.

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

1 Ответ

1 голос
/ 22 марта 2020

О ваших предположениях:

  1. Неясно, можно ли безопасно получить доступ к объекту канала из двух потоков.
  2. Обработчик сигнала выполняется в потоке, который излучает сигнал .

Я полагаю, что именно тот факт, что вы излучаете сигнал из другого потока, вызывает проблему.

Вы можете решить эту проблему с помощью GLib.idle_add(your_update_func). Вместо непосредственного вызова your_update_func запрос добавляется в основной файл Gtk l oop, который выполняет его, когда больше нет событий для обработки, предотвращая любые проблемы с потоками.

Подробнее здесь: https://wiki.gnome.org/Projects/PyGObject/Threading

...