Usando QtConcurrent para cargar un mapa de píxeles y pintarlo

Estoy intentando crear un programa de renderizado de mosaicos. Aquí hay un código básico.

Encabezamiento

class Tile: public QGraphicsItem
{
public:
Tile(void);
~Tile(void);
QGraphicsPixmapItem *tileItem;
void update(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
 protected:
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
};

CPP:

.Constructor etc
.
.

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {
        qDebug()<<"Loading Pixmap";
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        tileItem=new QGraphicsPixmapItem;
        tileItem->setPixmap(p); 
    }
    tileItem->paint(painter,option,widget);
}

Estoy intentando crear una aplicación que pegue mosaicos de una imagen grande en un QGraphicsScene. Pero cargar todos los mosaicos a la vez requiere mucho tiempo y mucha memoria. Así que estoy subclasificando QGraphicsItem y anulando la pintura. El método de pintura en la clase QGraphicsItem solo se llama cuando aparece dentro de QGraphicsView. Entonces, al cargar mi mosaico dentro de la pintura, básicamente puedo crear una aplicación que carga los mosaicos solo cuando aparecen a la vista. Esto está funcionando hasta ahora.

Para mejorar la experiencia del usuario, uso QtConcurrent para intentar cargar los mosaicos en un hilo separado. Así que aquí están los cambios que hice.

CPP

connect(&watcher,SIGNAL(finished()),this,SLOT(updateSceneSlot()));

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {   
        TilePainter=painter;
        TileOption=option;
        TileWidget=widget;
        qDebug()<<"Paint Thread id "<< QThread::currentThread();

        future=QtConcurrent::run(LoadTilePixmap,this);
        watcher.setFuture(future);
    }
    else
        tileItem->paint(painter, option, widget);

}    

Función LoadTilePixmap:

void LoadTilePixmap(Tile *temp,QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
qDebug()<<"Loading Pixmap";
QPixmap p("c:\\qt\\tile\\tile0-0.png");
temp->tileItem=new QGraphicsPixmapItem;
temp->tileItem->setPixmap(p);
qDebug()<<"Loaded Pixmap";
}


void Tile::updateSceneSlot()
{
    qDebug()<<"updateSceneSlot Thread id "<< QThread::currentThread();
    tileItem->paint(TilePainter, TileOption, TileWidget);
}

Este código debería funcionar, pero sigue fallando en tiempo de ejecución tan pronto como se llama a paint. Después de agregar puntos de interrupción, reduje el problema a temp->tileItem->paint(painter,option,widget); que causa el accidente.

La salida que obtengo es

Loading Pixmap 
Almost Loaded Pixmap 
First-chance exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.
Unhandled exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.

¿Alguien podría ayudarme y hacerme saber por qué falla el método lastline / paint? ¿Cómo puedo arreglarlo?

CÓDIGO EDITADO PARA ACTUALIZAR CAMBIOS

preguntado el 10 de mayo de 11 a las 13:05

Creo que se está encontrando con problemas generales de concurrencia. No tiene ninguna sincronización para las variables a las que intenta acceder en varios subprocesos. Hay que tener en cuenta que tras lanzar el futuro, la llamada vuelve de inmediato. Paint podría llamarse muchas veces mientras el otro hilo está funcionando. ¿Cómo sabe que 1) aún no ha programado el trabajo y 2) el trabajo está realmente terminado? Paint () solo prueba para ver si el puntero es nulo, pero ¿cómo sabe realmente que el otro hilo está listo? El hilo de la GUI probablemente se esté activando durante la creación del mapa de píxeles. -

Esta es la primera vez que intento realizar múltiples subprocesos, así que realmente no sé si lo estoy haciendo bien. Según la documentación concurrente de Qt, se supone que Qt se encarga de la sincronización de las variables. En segundo lugar, incluso probé un mecanismo de ranura de señal donde se usa una ranura en cola. Entonces, básicamente, después de que el hilo finaliza la ejecución, emite una señal que se coloca en una cola. La ranura (que llama pintura) se llama turno por turno. Creo que eso debería haber resuelto los problemas de concurrencia, aunque no lo sé. Lo resolví ahora llamando a update en lugar de paint. Actualizar programa una llamada de pintura. Funciona. -

3 Respuestas

Solo el hilo principal (también llamado GUI) puede dibujar en la pantalla. La siguiente línea de la función LoadTilePixmap (), que ejecuta en un hilo separado, creo, intenta pintar el contenido de su elemento de mapa de píxeles en la pantalla.

temp->tileItem->paint(painter,option,widget);

En el hilo solo debes cargar y preparar la imagen y cuando el hilo esté terminado, indica al hilo principal que la imagen está lista y haz el dibujo desde el hilo principal.

contestado el 10 de mayo de 11 a las 18:05

Ese no es el problema. Configuré un mecanismo de ranura de señal. Pero el programa aún falla. Hice una rápida obtención del hilo actual para encontrar qué hilo llama a paint. El hilo principal llama paint. Sigo recibiendo los mismos errores. Unhandled exception at 0x65256977 (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0x00000080. Aparece un cuadro emergente que me da dos opciones: romper o continuar. Al hacer clic en romper, me lleva a la línea 7130 de qpainter.cpp o la línea 2194 de qglobal.h. ¿Alguna idea de lo que está pasando? - durmiendo.ninja

Ese era el problema principal, pero no el único. Steffen vio una posible, y aquí hay otra. No puede crear el puntero al QGraphicsPixmapItem de esa manera. Debe pasar una referencia al puntero, entonces su función LoadTilePixmap () obtendrá un puntero a un puntero. En realidad, creo que será mucho más claro si la función LoadTilePixmap () no tiene ningún parámetro, simplemente cree un QGraphicsPixmapItem en él y devuelva un puntero a ese objeto, este puntero lo asignará a su miembro tileItem. - zkunov

Yo también lo intenté. Aún enfrenta el mismo problema. La sugerencia de Steffen no es un problema. Este mismo código funciona perfectamente si no utilizo subprocesos múltiples (los dos primeros fragmentos de código). - durmiendo.ninja

No está claro en el código que publicó, pero ¿está inicializando? tileItem ser NULL en el constructor de Tile? De lo contrario, esa sería una posible explicación del accidente que está viendo.

contestado el 11 de mayo de 11 a las 01:05

tileItem se establece en NULL en el constructor. Pero Qt es lo suficientemente inteligente como para no hacer nada con él, ya que no es visible. Tan pronto como se vuelve visible, cargo un mapa de píxeles que se puede ver. Esto funciona perfectamente. De hecho, los primeros 2 fragmentos de código son exactamente eso. Lo he probado mucho y funciona perfectamente. El único problema surge cuando agrego subprocesos múltiples e intento pintarlo. Si comento la última línea de pintura, mi código funciona bien. - durmiendo.ninja

Resolví el problema evitando pintar y en su lugar usando la función de actualización. De cualquier documentación que haya leído, la actualización programa indirectamente una llamada de pintura. Entonces, al final, mi código se ve así

void LoadTilePixmap(Tile *temp)
{
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        temp->tileItem=new QGraphicsPixmapItem;
        temp->tileItem->setPixmap(p);
        temp->update(0,0,511,511);
}

Esto hará que mi función de pintura sobrecargada sea llamada por segunda vez, pero esta vez la condición if es falsa y entra en else which paints. No es exactamente una solución óptima, pero funciona por ahora.

contestado el 12 de mayo de 11 a las 08:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.