Найти - Пользователи
Полная версия: Некоторые сигналы, посылаемые из потока пропадают. В PyQt бага?
Начало » GUI » Некоторые сигналы, посылаемые из потока пропадают. В PyQt бага?
1
dimabest
Очень прошу помочь.

Написал программу, которая прекрасно работала в одном потоке. Но после добавления рабочих потоков, время от времени стали выскакивать чудесные баги. Много часов провел за отладкой, расставил кучу принтов и…. получил такую штуку - время от времени сигналы высылаемые из рабочих потоков уходят вникуда. То есть сигнал высылается, а подписанный на него слот не вызывается.

Для проверки написал тестовую мини-программку, которая демонстрирует проблему.

Создаю очередь Queue, куда буду класть задания.
Создаю 4 потока Worker, которые в бесконечном цикле проверяют очередь.
Задание - объект простенького класса Task. Класс - наследуется от QObject, чтобы мог посылать сигнал ‘taskEnd’. Конструктор Task получает функцию, которую вызовет в потоке и параметр для этой функции.
func - функция, которая будет вызвана в потоке. Она параметром получает число, прибавляет к нему 1 и возвращает результат.
TASK_NUMBER - количество заданий, то есть количество объектов Task, которые будут обрабатыватся потоками.

Как пример работает:

щелкаем по кнопке ‘start’ - программа создает TASK_NUMBER объектов заданий, подписывает на сигнал ‘taskEnd’ обработчик - метод формы success, и кладет объект задания в Queue.

свободный поток забирает задание и запускает его. Метод run объекта задания вызывает “рабочую” функцию func, а результат ее выполнения посылает с сигналом ‘taskEnd’.
Метод success, который подписан на сигнал, считает сколько раз был вызван….

Проблема в том, что на 100 000 заданий примерно в четырех случаях из пяти приходится 99 996 - 99 999 вызовов метода success.

В PyQt бага или я чего-то не понимаю?

Python 2.6
Qt 4.6
PyQt 4.7
Windows Vista

Код:
# coding: utf-8

import sys
import Queue
import time

from threading import Thread

from PyQt4.QtGui import *
from PyQt4.QtCore import *


TASK_NUMBER = 100000

def func(i):
return i + 1

class Worker(Thread):
def run(self):
while True:
try:
t = queue.get()
except Queue.Empty:
continue
else:
time.sleep(0.02)
t.run()

class Task(QObject):
def __init__(self, target=None, args=()):
QObject.__init__(self)
self.target = target
self.args = args

def run(self):
r = self.target(*self.args)
self.emit(SIGNAL('taskEnd(PyQt_PyObject)'), r)

class Form(QWidget):
def __init__(self, queue):
QWidget.__init__(self)
self.queue = queue
self.get = 0
self.tasks = []

self.button = QPushButton('start')
QObject.connect(self.button, SIGNAL('clicked()'), self.start)
self.put_label = QLabel('Put: 0')
self.get_label = QLabel('Get: 0')

v = QVBoxLayout()
v.addWidget(self.put_label)
v.addWidget(self.get_label)
v.addWidget(self.button)
self.setLayout(v)

def start(self):
for i in xrange(TASK_NUMBER):
t = Task(func, args=[i])
self.tasks.append(t)
QObject.connect(t, SIGNAL('taskEnd(PyQt_PyObject)'), self.success)
self.queue.put(t)

self.put_label.setText('Put: ' + str(TASK_NUMBER))


def success(self, r):
self.get += 1
self.get_label.setText('Get: ' + str(self.get))



queue = Queue.Queue()

t1 = Worker()
t1.start()
t2 = Worker()
t2.start()
t3 = Worker()
t3.start()
t4 = Worker()
t4.start()


app = QApplication(sys.argv)
f = Form(queue)
f.show()
sys.exit(app.exec_())
dimabest
фух, после множества экспериментов разобрался.

1. Если нужно посылать сигнал из потока - то посылать его должен не объект задания, а объект потока!
В таком случае все слоты вызываются.

Соответственно поток нужно наследовать от QThread, а программу немного переделать. Например, в класс Task передавать не только “рабочую” функцию, а и callback-функцию, которая будет вызвана в главном потоке по завершению задания.

2. Второй вариант - посылать из потоков не сигналы, а события методом QApplication.postEvent(receiver, event)

Запускал оба варианта по десятку раз на 100 тыс заданий. Все слоты в главном потоке были вызваны!
psyh0y
Здравствуйте dimabest, если вам не сложно можете скинуть пример того, как вы решили этот вопрос.
Singularity
Он больше года сюда не заходил. Попробуй написать на мыло (ссылка возле ника), хотя я бы забыл что я делал три года назад
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB