已解决:尝试将线程与GTK和Pycairo一起使用时出现错误(绘制窗口并从另一个线程发出信号)



解决方案

  • 删除通道和相关代码
  • 在窗口类中添加一个新的更新函数,该函数将新形状作为参数
  • 修改类的初始化
  • 调用更新函数

解决方案的修改

很抱歉,但diff标记似乎没有正确显示,希望你仍然应该了解解决方案是如何工作的

Window类

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)

一种新的类方法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()

问题

Window类

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时重新绘制形状列表,并且在构造函数的shapes_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((对象,名为shapes_Channel
  2. 创建了一个新窗口,shapes_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对象按预期工作,但可能仅仅是存在一个跨线程共享的对象就让整个事情陷入混乱
  2. 即使它在主线程上,它也不喜欢有其他线程

我真的很感激能为这件令人抓狂的事情提供一些指导。

关于您的假设:

  1. 尚不清楚您的通道对象是否可以从两个线程安全访问
  2. 信号处理程序在发出信号的线程中执行

我的猜测是,是您从另一个线程发出信号导致了问题。

您可以使用GLib.idle_add(your_update_func)来解决此问题。Gtk主循环中没有直接调用your_update_func,而是添加了一个请求,当没有更多事件要处理时,Gtk会执行该请求,从而防止出现任何线程问题。

点击此处阅读更多信息:https://wiki.gnome.org/Projects/PyGObject/Threading

最新更新