Форум сайта python.su
Очень прошу помочь.
Написал программу, которая прекрасно работала в одном потоке. Но после добавления рабочих потоков, время от времени стали выскакивать чудесные баги. Много часов провел за отладкой, расставил кучу принтов и…. получил такую штуку - время от времени сигналы высылаемые из рабочих потоков уходят вникуда. То есть сигнал высылается, а подписанный на него слот не вызывается.
Для проверки написал тестовую мини-программку, которая демонстрирует проблему.
Создаю очередь 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_())
Отредактировано (Март 16, 2010 09:52:30)
Офлайн
фух, после множества экспериментов разобрался.
1. Если нужно посылать сигнал из потока - то посылать его должен не объект задания, а объект потока!
В таком случае все слоты вызываются.
Соответственно поток нужно наследовать от QThread, а программу немного переделать. Например, в класс Task передавать не только “рабочую” функцию, а и callback-функцию, которая будет вызвана в главном потоке по завершению задания.
2. Второй вариант - посылать из потоков не сигналы, а события методом QApplication.postEvent(receiver, event)
Запускал оба варианта по десятку раз на 100 тыс заданий. Все слоты в главном потоке были вызваны!
Офлайн
Здравствуйте dimabest, если вам не сложно можете скинуть пример того, как вы решили этот вопрос.
Офлайн
Он больше года сюда не заходил. Попробуй написать на мыло (ссылка возле ника), хотя я бы забыл что я делал три года назад
Офлайн