多线程:在GUI线程之外使用像素图不安全。

5
我正在构建一个音乐播放器,使用SqueezePlay进行状态检查,它是一个SqueezeBox控制器应用程序。简而言之,我通过线程每5秒钟检查一次Squeezeplay的状态。如果歌曲标题更改,我会让其更新标签(Qlabel)、专辑封面(QPixmap)等。然而,当我要求它通过线程更新时,我得到了“在GUI线程外部使用像素图不安全”的错误信息。
如何在使用线程的同时设置QPixmap?
示例代码:
#self.sq.getArtwork() returns variable with the image
coverArt = self.sq.getArtwork()
coverPixMap = QtGui.QPixmap()
coverPixMap.loadFromData(coverArt)
self.albumArt.setPixmap(coverPixMap)

非常感谢!

更新: 我尝试使用Emit进行以下操作,但它不起作用,有人可以看看我做错了什么吗?

def setNewArtwork(self, image):
    coverPixMap = QtGui.QPixmap()
    coverPixMap.convertFromImage(image)
    icon = QtGui.QIcon(coverPixMap)
    item.setIcon(icon)

def getNewArtwork(self):
    coverArt = self.sq.getArtwork()
    icon = QtGui.QImage(coverArt)
    self.emit(QtCore.SIGNAL('setNewArtwork(QImage)'), icon)

1
使用QImage而不是QPixmap - smerlin
self.emit(QtCore.SIGNAL('setNewArtwork(QImage)'), icon) 这一行应该使用 QPixmap 而不是 QImage,因为你正在使用像素图。我不知道类型有多么严格执行。同时,请确保使用了队列连接(如果 PyQT 中存在这样的概念)。这可以确保槽在接收器线程中运行,而不是调用者线程中运行。 - Elliott
当你说信号“不起作用”时,它以什么方式不起作用?出了什么问题?是同样的问题吗?还是其他问题? - Elliott
@FLX:你找到解决方案了吗?我很感兴趣。 - neydroydrec
4个回答

7

所有的Qt图形操作都应该发生在主线程中。其他线程不允许调用Qt图形操作(包括可能的像素映射)。

它们可以发出Qt信号到主线程。或者简单地(在Linux上)写入一个管道,并让主线程在该管道上等待输入。

当然,您必须定义您想要的信号(以及插槽)。在C++代码中,您需要使用signals:(或slots:)标记它们,您的C++代码应该由moc处理。我不知道Python的对应物是什么(也许Python反射能力足够了,我真的不知道)。然后,您必须使用queued连接将信号连接到插槽。我不知道如何在Python中做到这一点。


我有点新手,这该怎么做呢?我真的需要一些示例代码 :) - FLX
我添加了一些链接。Qt 文档比我在几分钟内所能解释的要好得多! - Basile Starynkevitch
谢谢Basile,我已经尝试过了,但它不起作用;请看我在原帖中的示例 - 我做错了什么愚蠢的事情? - FLX
你可能需要定义你的信号和槽,并将信号连接到槽。你应该找出如何在Python中实现这一点(或者使用C++编写你的应用程序,或者使用GTK...)。 - Basile Starynkevitch

4
回答有关如何在Python中发出信号的问题:
与C++不同,当发出自定义PyQt信号(而不是Qt信号)时,应省略签名。
因此,要发出信号,请执行以下操作:
thread.emit(QtCore.SIGNAL('newArtworkAvailable'), icon)

要连接到信号,请执行以下操作:

widget.connect(thread, QtCore.SIGNAL('newArtworkAvailable'),
               widget.setNewArtwork)

需要明确的是:

为了使这个工作正常,非GUI线程必须发出信号,然后由主GUI线程中的适当小部件接收。在非GUI线程中创建 QImage 应该没问题,但不要尝试在主线程之外调用任何GUI相关的方法。

NB

我在这里使用了旧式信号语法,因为这似乎是您正在使用的。然而,您可能需要查看PyQt的新式信号和槽支持,因为它更加灵活和符合Python风格。


1

你可能需要将所有的绘制任务发送到主线程。


0

我尝试过一些东西,请告诉我是否有所启发,我也曾为我的项目做过类似的事情(但是我已经离开 Python 有一段时间了,所以我可能也犯了错误,如果是这样,抱歉)。

class MyThread(QThread, ui):
    def __init__(self, ui):
        super(MyThread, self).__init__(self)
        self.ui = ui

    def run(self):
       coverArt = self.ui.getArtwork()
       coverPixMap = QtGui.QPixmap()
       coverPixmap.convertFromImage(QtGui.QIcon(coverArt))
       icon = QtGui.QImage(coverPixMap)
       self.ui.item.setIcon(icon)  // set icon
       self.ui.singerLabel.setText("Singer")  // update label 

# your gui class
class YourInterface(QtGui.QWidget):
    def __init__(self):
       QtGui.QWidget.__init__(self)
        myThread = MyThread(self)
        self.myButton.clicked.connect(myThread.run)
        # all other stuff
        #
        #

这个不起作用。它会返回类似于:QPixmap: 在GUI线程之外使用像素图是不安全的 - neydroydrec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接