É possível forçar a execução do thread principal (thread GUI) do thread de renderização?

Sep 19 2020

Temos fila de comandos que são executados no thread de renderização, diretamente entre QQuickWindow::beforeRenderinge QQuickWindow::afterRendering, os comandos fazem alterações no modelo de dados e, quando o modelo de dados muda, ele notifica imediatamente nosso mecanismo de renderização OpenGL personalizado para sincronizar os dados.

O problema é que quando o modelo de dados muda, ele também notifica os assinantes que pretendem atualizar a IU. Mas é uma abordagem sujeita a erros para atualizar a IU a partir de um thread diferente. Uma maneira é usar Qt::QueuedConnection. Isso também está sujeito a erros porque, quando é executado, o modelo pode ir para o estado distante.

O design é muito semelhante a este exemplo .

É possível, por exemplo, atualizar QStadardItemModelvinculado a QML do thread de renderização?

Respostas

3 GrecKo Sep 19 2020 at 03:07

É possível, você pode fazer com a QMetaObject::invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)sobrecarga de QMetaObject::invokeMethod.

Para o contexto, você precisa passar um objeto vivo no thread em que deseja executar sua função. Para o thread da GUI, QCoreApplication::instance()é um bom candidato. Para o tipo de conexão, use Qt::QueueConnectionou Qt::BlockingQueuedConnectiondependendo de suas necessidades (ou você precisa da chamada para bloquear ou não). Se estiver usando Qt::BlockingQueuedConnection, certifique-se de que não esteja no thread principal (você pode fazer uma verificação e passar Qt::DirectConnectionse for o caso).

E para o functor, um lambda fará o truque.

Por exemplo:

qDebug() << "1 main thread" << QThread::currentThreadId();
QtConcurrent::run([] {
    qDebug() << "1 QtConcurrent thread" << QThread::currentThreadId();

    QMetaObject::invokeMethod(QCoreApplication::instance(), [] {
        qDebug() << "invokeMethod thread" << QThread::currentThreadId();
    }, Qt::BlockingQueuedConnection);

    qDebug() << "2 QtConcurrent thread" << QThread::currentThreadId();
});
qDebug() << "2 main thread" << QThread::currentThreadId();

Isso resulta em:

1 main thread 0x1c7c
2 main thread 0x1c7c
1 QtConcurrent thread 0x19ec
invokeMethod thread 0x1c7c
2 QtConcurrent thread 0x19ec
2 BasileStarynkevitch Sep 19 2020 at 02:59

Você pode se inspirar chamando funções Qt de manipuladores de sinais Unix

Você pode ter alguma estrutura de dados central, por exemplo, algumas std::dequecontendo expressões lambda ; vamos chamá-lo de sua lista de tarefas. Essa lista de tarefas também gerencia um pipe (7) (dois descritores de arquivo).

Você protegeria essa lista de tarefas com o std::mutexuso adequado estd::condition_variable

Você sincronizaria entre o thread de renderização e o thread principal do Qt usando algum pipe (7) ou fifo (7) , então use (no thread principal) algum QSocketNotifier para sincronização. O thread de renderização também escreveria (2) algum byte naquele fifo ou pipe ao adicionar um fechamento à sua lista de tarefas, e o thread principal usaria QSocketNotifierpara lê-lo (2) , em seguida, buscar (abrir) um fechamento de sua lista de tarefas e executar isto.