Qt Slots频繁调用

时间:2016-01-03 22:58:59

标签: c++ multithreading qt signals-slots qgraphicsscene

我有一个工作线程,可以处理繁重和长时间的计算(最多十分之一秒)。这些计算产生数千个QLine s,代表动态增长树的边缘。 这些边可以随时修改,因为它们通过检查由距离表示的成本来连接树的节点。 我想要包含边缘的QGraphicsScene的平滑更新。 我尝试了信号和插槽:

  • 工作线程发出一个信号,所以当缓冲区已满时,主线程会捕获此信号,这将处理线路的更新/绘制
  • 这个信号仍然被主线程捕获,但它似乎经常发出,所以QGraphicsViewQLine窒息而被添加
  • 更改缓冲区的大小并不重要
  • 有替代方法吗?

主要插槽是:

void MainWindow::update_scene(bufferType buffer)
{
  for (int i = 0; i < buffer.size(); ++i)
  {
      if (buffer[i].first < (g_edges.size() - 1))
      {
          delete g_edges[buffer[i].first];
          g_edges[buffer[i].first] = scene->addLine(buffer[i].second);
      }
      else
          g_edges.push_back(scene->addLine(buffer[i].second));
   }
}

请注意,bufferType的类型为QList<std::pair<int,QLine>>。 这是重型计算部分

while (T.size() < max_nodes_number && !_stop)
{
    const cnode random_node = rand_conf ();
    const cnode nearest_node = T.nearest_node (random_node);
    cnode new_node = new_conf (nearest_node, random_node);

    if (obstacle_free(nearest_node, new_node))
    {
        QList<cnode*> X_near = T.neighbours (new_node, max_neighbour_radius);
        cnode lowest_cost_node = nearest_node;
        qreal c_min = nearest_node.cost() + T.distance (nearest_node, new_node);

        for (int j = 0; j < X_near.size(); ++j)
        {
            if (obstacle_free(*X_near[j], new_node) && ((X_near[j]->cost() + T.distance (*X_near[j], new_node)) < c_min))
            {
                c_min = X_near[j]->cost() + T.distance (*X_near[j], new_node);
                lowest_cost_node = *X_near[j];
            }
        }

        T.add_node (new_node, lowest_cost_node.id());
        queue (new_node.id(), QLine (new_node.x(), new_node.y(), lowest_cost_node.x(), lowest_cost_node.y()));

        for (int j = 0; j < X_near.size(); ++j)
        {
            if (obstacle_free(*X_near[j], new_node) && (new_node.cost() + T.distance (new_node, *X_near[j])) < X_near[j]->cost())
            {
                queue (X_near[j]->id(), QLine (new_node.x(), new_node.y(), X_near[j]->x(), X_near[j]->y()));

                T.update_parent (*X_near[j], new_node.id());
                T.rewire_tree (X_near[j]->id());
            }
        }
    }
}
emit finished();

请注意T是代表树的类。它由允许添加节点,搜索最近的节点等的一些方法构成。它具有QList<cnode>作为私有成员,存储树的节点。 cnode是由两个坐标构成的结构,id,父母,成本,子女列表。

1 个答案:

答案 0 :(得分:2)

解决方案与往常一样 - 避免频繁排队连接,因为这些连接非常慢。排队连接是粗粒度构造,因此可以这样使用。

批量处理工作。在您的方案中,您可以聚合容器中的计算行,并且只有当它达到某个阈值时,才将该容器传递给主线程以绘制/更新行。阈值可以是计数,时间或两者的组合,如果只有少量结果需要更新,则不希望不更新。您将需要扩展您的设计以将while循环拆分为在线程事件循环中运行而不是阻塞,以便您可以定期聚合和传递更新 - 类似于this。对于需要时间的工人来说,这总是一个好主意 - 你可以监控进度,取消,暂停和各种方便的东西。

这两行看起来很可疑:

    edges.removeAt(i);
    edges.insert (i, scene->addLine (l));

然后你删除然后插入 - 这是一个潜在的昂贵的重新分配的邀请,即使没有重新分配,也会涉及不必要的复制。您可以简单地替换该索引处的元素,而不是删除和插入。

在您的情况下,您可能会省略拆分实际的while循环。只是不要在循环中发出,而是做这样的事情(伪代码):

while(...) {
    ...
    queue(new line)
    ...
    queue(update line)
    ...
    queue(final flush)
}

void queue(stuff) {
    stuffBuffer.append(stuff)
    if (stuffBuffer.size() > 50 || final_flush) {
        emit do_stuff(stuffBuffer) // pass by copy
        stuffBuffer.clear() // COW will only clear stuffBuffer but not the copy passed above
    } 
}

或者它会让你感觉更好:

    copy = stuffBuffer
    stuffBuffer.clear()
    emit do_stuff(copy)

这样,在发出副本之前,两个容器将从共享数据中分离出来。

编辑:经过长时间的讨论后,我最终提出了一些设计更改以提高性能(排队连接只是性能问题的一个方面):

  • 缓解图形场景 - 在“每行一项”和“所有行一项”解决方案之间找到折衷方案,其中每个项目处理其直接子项的线条绘制,在CPU时间之间取得平衡用于向场景添加项目和重新绘制数据更改项目。

  • 禁用自动场景更新,而是显式控制场景更新,这样就不会为每一个微小的变化更新场景。

  • 批量聚合查看命令并以固定间隔提交工作缓冲区,以避免排队信号开销。