1.目标实现一个子线程,防止ROS2在通讯过程中使用spin()对主线程的UI进行堵塞!!
因为,在主线程执行spin的时候会阻塞,所以我们想要通过多线程解决这个问题,同时可以通过主线程按键信号去控制话题的同断
首先我们已经有了话题文件:
wang2.h
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include "std_msgs/msg/u_int32.hpp"
#include <QApplication>
#include <QThread>
#include <QPushButton>
#include <QSignalMapper>
class SingleDogNode : public rclcpp::Node
{
public:
explicit SingleDogNode(const std::string& name);
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_novel;//声明创建分开使用共享指针,这里,rclcpp::Subscription 是模板类,需要指定模板参数 std_msgs::msg::String 来表明订阅的消息类型是字符串类型。SharedPtr 是智能指针的一种,用于自动管理对象的生命周期,防止内存泄漏。
rclcpp::Publisher<std_msgs::msg::UInt32>::SharedPtr pub_money;
void topic_callback(const std_msgs::msg::String::SharedPtr msg);
};
wang2.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include "std_msgs/msg/u_int32.hpp"
#include <QApplication>
#include <QThread>
#include <QPushButton>
#include <QSignalMapper>
#include "wang2.h"
SingleDogNode::SingleDogNode(const std::string& name)
: Node(name) // 这里添加分号
{
// 打印一句自我介绍
RCLCPP_INFO(this->get_logger(), "大家好,我是单身狗%s.", name.c_str());
// 创建一个订阅者来订阅李四写的小说,通过名字 sexy_girl
sub_novel = this->create_subscription<std_msgs::msg::String>("sexy_girl", 10, std::bind(&SingleDogNode::topic_callback, this, std::placeholders::_1));
// 创建发布者
pub_money = this->create_publisher<std_msgs::msg::UInt32>("sexy_girl_money", 10);
}
void SingleDogNode::topic_callback(const std_msgs::msg::String::SharedPtr msg)
{
// 新建一张人民币
std_msgs::msg::UInt32 money;
money.data = 10;
// 发送人民币给李四
pub_money->publish(money);
RCLCPP_INFO(this->get_logger(), "朕已阅:'%s',打赏李四:%d 元的稿费", msg->data.c_str(), money.data);
}
随后为了使得这个节点可可以在子线程中运行,因此我们创立一个子线程的运行空间
roswprker.h
#ifndef ROSWORKER_H
#define ROSWORKER_H
#include <QObject>
#include <QThread>
#include <memory>
#include <rclcpp/rclcpp.hpp>
class ROSWorker : public QObject
{
Q_OBJECT
public:
explicit ROSWorker(std::shared_ptr<rclcpp::Node> node, QObject *parent = nullptr);
~ROSWorker();
public slots:
void start();
void stop();
signals:
void finished();
private:
std::shared_ptr<rclcpp::Node> node_;
QThread workerThread_;
bool isRunning_;
};
#endif // ROSWORKER_H
rosworker.cpp文件
#include "rosworker.h"
#include <QDebug>
ROSWorker::ROSWorker(std::shared_ptr<rclcpp::Node> node, QObject *parent)
: QObject(parent), node_(node), isRunning_(false)
{
// 将ROSWorker对象移动到工作线程中
moveToThread(&workerThread_);
}
ROSWorker::~ROSWorker()
{
workerThread_.quit();
workerThread_.wait();
}
void ROSWorker::start()
{
if (!isRunning_) {
isRunning_ = true;
// 确保每次启动前重新初始化 ROS 2 上下文
/* if (!rclcpp::ok()) {//判读有无初始化,如果没有那么就重新初始化防止出现未初始化导致崩溃
rclcpp::init(0, nullptr);
}*/
// 开始工作线程
workerThread_.start();
qDebug() << "open1";
// 开始执行工作
connect(&workerThread_, &QThread::started, this, [this]() {
qDebug() << "open2";
rclcpp::spin(node_);
emit finished();
isRunning_ = false;
});
}
}
void ROSWorker::stop()
{
if (isRunning_) {
// 停止执行工作
qDebug() << "close";
rclcpp::shutdown();
isRunning_ = false;
workerThread_.quit();
workerThread_.wait();
}
}
在mainwindow的使用 :(这里一定要注意!!rclcpp::init()与rclcpp::shutdown()成对出现!!因此为了避免重复初始化以及重复关闭情况所以需要用if进行判断,从而方式程序崩溃)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <rclcpp/rclcpp.hpp>
#include <src/wang2.h>
#include "runnodes/rosworker.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 按钮启动ROSWorker
connect(ui->pushButton, &QPushButton::clicked, this,[&]()
{
// 初始化ROS 2
if (!rclcpp::ok()) {//判读有无初始化,如果没有那么就重新初始化防止出现未初始化导致崩溃
rclcpp::init(0, nullptr);
// 创建ROSWorker对象
auto node = std::make_shared<SingleDogNode>("wang2");
rosWorker = new ROSWorker(node);
}
rosWorker->start();
});
// 按钮停止ROSWorker
connect(ui->pushButton_1, &QPushButton::clicked,this,[&](){rosWorker->stop();});
}
MainWindow::~MainWindow()
{
rosWorker->stop();
rclcpp::shutdown(); // 确保在退出时关闭ROS 2上下文
delete ui;
}
效果展示:当没有点击按钮的时候没有节点产生
打开之后会初始化节点:
打开另外一个节点:
可以正常通讯(这时候疯狂点击也不会出现崩溃现象!)
然后点击关闭节点(疯狂点击也不会崩溃!!如果不加if判断很有可能会崩溃)可以看见正常关闭
并且可以重复打开: