已解决:尝试将线程与 GTK 和 Pycairo 一起使用时出现错误(绘制 window 并从另一个线程发出信号)
SOLVED: I get an error when trying to use threading with GTK & Pycairo (to draw a window and signal it from another thread)
解决方案
- 删除频道和关联代码
- 在 window class 中添加一个新的更新函数,它将新的形状作为参数
- 修改class
的初始化
- 调用更新函数
解决方案的修改
抱歉,diff markdown 似乎没有正确显示,希望您仍然应该了解解决方案的工作原理
Window class
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()
在class方法中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)
一个新的class方法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 class
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)
我正在尝试 运行 可以绘制我定义的形状(直线和多边形)的 window。
在我向它提供形状列表并在我的应用程序结束时 运行 它工作之前它工作正常
但是,我正在尝试添加交互性并让它在 update_signal 被调用时重绘形状列表,并且新形状列表沿着 shapes_channel 传递,这是 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()
如您所见:
- 创建了一个 Channel() 对象,命名为 shapes_channel
- 创建了一个新的 window,shapes_channel 通过中间函数 new_window 传递给构造函数。
- 这个window传递给另一个线程,让另一个线程
可以发出相关信号("update_signal")
- 另一个线程是运行
- 主线程中的 window 是 运行 我得到以下控制台输出:
UPDATE SIGNAL CALLED
Num new shapes: 31
Gdk-Message: 01:27:14.090: main.py: Fatal IO error 0 (Success) on X server :0.
从控制台输出,我们可以推断信号被成功调用,新的形状被传递到 window 并正确存储,但在 self.show_all()
行失败。
这是一个以前运行良好并生成图形输出的对象,从对象的角度来看,我只能想到 2 种可能发生变化的事情:
- Channel 对象按预期工作,但也许仅仅存在跨线程共享的对象就会使整个事情变得混乱
- 虽然在主线程,但不喜欢有其他线程
对于这起令人抓狂的事件,我非常希望能得到一些指导。
关于你的假设:
- 不清楚您的通道对象是否可以从两个线程安全访问。
- 信号处理程序在发出信号的线程中执行。
我的猜测是您从另一个线程发出信号导致了这个问题。
您可以使用 GLib.idle_add(your_update_func)
解决此问题。不是直接调用 your_update_func
,而是将一个请求添加到 Gtk 主循环,当没有更多事件要处理时执行它,防止任何线程问题。
在此处阅读更多内容:https://wiki.gnome.org/Projects/PyGObject/Threading
解决方案
- 删除频道和关联代码
- 在 window class 中添加一个新的更新函数,它将新的形状作为参数
- 修改class 的初始化
- 调用更新函数
解决方案的修改
抱歉,diff markdown 似乎没有正确显示,希望您仍然应该了解解决方案的工作原理
Window class
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()
在class方法中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)
一个新的class方法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 class
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)
我正在尝试 运行 可以绘制我定义的形状(直线和多边形)的 window。
在我向它提供形状列表并在我的应用程序结束时 运行 它工作之前它工作正常
但是,我正在尝试添加交互性并让它在 update_signal 被调用时重绘形状列表,并且新形状列表沿着 shapes_channel 传递,这是 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()
如您所见:
- 创建了一个 Channel() 对象,命名为 shapes_channel
- 创建了一个新的 window,shapes_channel 通过中间函数 new_window 传递给构造函数。
- 这个window传递给另一个线程,让另一个线程 可以发出相关信号("update_signal")
- 另一个线程是运行
- 主线程中的 window 是 运行 我得到以下控制台输出:
UPDATE SIGNAL CALLED
Num new shapes: 31
Gdk-Message: 01:27:14.090: main.py: Fatal IO error 0 (Success) on X server :0.
从控制台输出,我们可以推断信号被成功调用,新的形状被传递到 window 并正确存储,但在 self.show_all()
行失败。
这是一个以前运行良好并生成图形输出的对象,从对象的角度来看,我只能想到 2 种可能发生变化的事情:
- Channel 对象按预期工作,但也许仅仅存在跨线程共享的对象就会使整个事情变得混乱
- 虽然在主线程,但不喜欢有其他线程
对于这起令人抓狂的事件,我非常希望能得到一些指导。
关于你的假设:
- 不清楚您的通道对象是否可以从两个线程安全访问。
- 信号处理程序在发出信号的线程中执行。
我的猜测是您从另一个线程发出信号导致了这个问题。
您可以使用 GLib.idle_add(your_update_func)
解决此问题。不是直接调用 your_update_func
,而是将一个请求添加到 Gtk 主循环,当没有更多事件要处理时执行它,防止任何线程问题。
在此处阅读更多内容:https://wiki.gnome.org/Projects/PyGObject/Threading