用事件队列解决QT GUI的操作顺序问题

原文: https://www.cnblogs.com/Philip-Tell-Truth/p/6295186.html

GUI操作顺序问题引发异常

有时候我们使用写GUI程序的时候会遇到这样的问题:比如在程序中,建立了一个列表的GUI。这个列表是随着时间不断更新的,而且操作也会读取这个列表GUI的内容。

如果这个程序是多线程的程序,而且只是除了GUI的线程不操作,只是其他线程操作这个列表GUI,那么这个问题很简单,只用加互斥锁就可以了。但如果GUI线程自己本身也要操作这个列表,那么这个问题就很麻烦了。

我们可以很容易地想到一种场景,比如GUI线程读了列表的一些表项(比如选定),此时线程中的某个方法keep了这些表项的指针,然而此时很不幸别的线程有一个请求需要删除列表中的一些表项,并且这些表项有一些包含在了我们的选定内容里,我们知道几乎所有的语言操作GUI时都要进入GUI线程里面操作,那么我们刚才选定表项的那个方法会被打断,然后进入删除表项方法,在删除了表项以后再次回到选定表项方法时,我们的选定的表项有一些已经被删除了,此时我们再进行操作很有可能不符合我们的要求。

如果你是用一般是用C#,JAVA这种不用自己管理内存的语言,那还好,只是结果可能不对,但是如果是用C++这种需要我们自己管理内存的来写,很有可能我们会操作一个被释放了内存的对象,然后程序崩掉,这样的情况是我们不想看到的。

用事件队列来解决问题:

下面用一幅图来表示如何设计事件队列:

  当然图中虽然是只有三种操作,如果你想,你可以设计出更多的操作,比如读操作,你可以细分为复制表项中的信息和给表项中对应的内容进行操作等。

这样设计以后,就一定程度上消除了GUI打断操作的问题(比如我们会再遇到我们上面的那种访问了一个被析构了的对象问题)。

  在Qt中我们可以这样写:(ConnectionView这个对象就是我上面说的那种表项)

class ItemsOpsBase
    {
    public:
        virtual void doOperation(ConnectionView *view) = 0;
        virtual ~ItemsOpsBase() = default;
    };

    class DeleteItem : public ItemsOpsBase
    {
    public:
        DeleteItem(qintptr target,qint32 connectionIndex)
            :ItemsOpsBase(), _target(target),_connectionIndex(connectionIndex){ }

        void doOperation(ConnectionView *view)override;
        ~DeleteItem() = default;
    private:
        qintptr _target;
        qint32 _connectionIndex;
    };

    class UpdatePulse :public ItemsOpsBase
    {
    public:
        UpdatePulse(qintptr descriptor,qint32 currentTime)
            :ItemsOpsBase(), _descriptor(descriptor),_currentTime(currentTime){  }

        void doOperation(ConnectionView *view)override;
        ~UpdatePulse() = default;
    private:
        qintptr _descriptor;
        qint32 _currentTime;
    };

class UpdateRemark : public ItemsOpsBase
    {
    public:
        UpdateRemark(qintptr descriptor, const QString &remark)
            : ItemsOpsBase(),_remark(remark),_descriptor(descriptor){  }

        void doOperation(ConnectionView *view)override;
        ~UpdateRemark() = default;
    private:
        QString _remark;
        qintptr _descriptor;
    };

    class TestConnection : public ItemsOpsBase
    {
    public:
        void doOperation(ConnectionView *view)override;
    };
class TestConnectionProducer : public QThread
    {
    public:
        void run()override;
    };

    class CopySelectedItemInformProducer :  public QThread
    {
    public:
        void run()override;
    };

    class DisconnectTargetsProducer : public QThread
    {
    public:
        void run()override;
    };

    class DeleteItemProducer :public QThread
    {
    public:
        DeleteItemProducer(qintptr target, qint32 connectionIndex)
            : QThread(),_target(target),_connectionIndex(connectionIndex) { }
        void run()override;
    private:
        qintptr _target;
        qint32 _connectionIndex;
    };

    class UpdatePulseProducer :public QThread
    {
    public:
        UpdatePulseProducer(qintptr descriptor, qint32 currentTime)
            :QThread(),_descriptor(descriptor),_currentTime(currentTime){  }
    protected:
        void run()override;
    private:
        qintptr _descriptor;
        qint32 _currentTime;
    };

    class UpdateRemarkProducer : public QThread
    {
    public:
        UpdateRemarkProducer(qintptr descriptor, const QString &remark)
            :QThread(),_remark(remark),_descriptor(descriptor){ }
    protected:
        void run()override;
    private:
        QString _remark;
        qintptr _descriptor;
    };
class ConsumerHelper :public QThread
    {
    public:
        ConsumerHelper(ConnectionView *view)
            :QThread(),_view(view){ }
        ~ConsumerHelper();
    protected:
        void run() override;
    private:
        ConnectionView *_view;

        ConsumerHelper(const ConsumerHelper &other) = delete;
        ConsumerHelper(const ConsumerHelper &&other) = delete;
        ConsumerHelper &operator=(const ConsumerHelper &other) = delete;
    };

  互斥锁以及队列的代码:

static QQueue<QSharedPointer<ItemsOpsBase>> &opQueue()
{
       static QQueue<QSharedPointer<ItemsOpsBase>> queue;
       return queue;
}

static QSharedPointer<ItemsOpsBase> endOperation;

static QMutex &opQueueLock()
{
       static QMutex mutex;
       return mutex;
}
static QWaitCondition &opQueueIsAvailable()
{
       static QWaitCondition flag;
       return flag;
}

  ConsumerHelper是一个消费者线程,一直监视着队列的动向,当需要一个某个操作的时候,我们就可以引发一个对象操作的线程,把对应操作加入队列中(为什么需要开一个线程,是为了方便互斥),比如下面我需要一个删除操作:

  删除操作的代码:

void DeleteItem::doOperation(ConnectionView *view)
    {
        qRegisterMetaType<qintptr>("qintptr");
        qRegisterMetaType<TcpConnectionHandler *>("TcpConnectionHandler *");
        QMetaObject::invokeMethod(view, "deleteConnection",Qt::QueuedConnection, Q_ARG(qintptr, _target), Q_ARG(qint32, _connectionIndex));
    }
void DeleteItemProducer::run()
    {
        QSharedPointer<ItemsOpsBase> op = QSharedPointer<ItemsOpsBase>(new DeleteItem(_target,_connectionIndex));

        QMutexLocker locker(&opQueueLock());
        opQueue().enqueue(op);
        opQueueIsAvailable().wakeOne();
    }

消费者线程的代码:

void ConsumerHelper::run()
    {
        forever
        {
            QSharedPointer<ItemsOpsBase> opPointer;

            {
                QMutexLocker locker(&opQueueLock());

                if (opQueue().isEmpty())
                    opQueueIsAvailable().wait(&opQueueLock());
                opPointer = opQueue().dequeue();

                if (opPointer == endOperation)
                    break;
            }
            {
                if(!opPointer.isNull())
                    opPointer->doOperation(_view);
            }
        }
    } 

    ConsumerHelper::~ConsumerHelper()
    {
        {
            QMutexLocker locker(&opQueueLock());
            while(!opQueue().isEmpty())
                opQueue().dequeue();

            opQueue().enqueue(endOperation);
            opQueueIsAvailable().wakeOne();
        }

        wait();//注意这里是wait在次线程上的
    }

  这个时候我只需要在需要用到删除操作的地方用:

DeleteItemProducer *deleteItemProducer = new DeleteItemProducer(target,index);
connect(deleteItemProducer, &QThread::finished, deleteItemProducer, &QThread::deleteLater);
deleteItemProducer->start();

原文地址:https://www.cnblogs.com/schips/p/12536427.html

时间: 2024-11-28 23:24:42

用事件队列解决QT GUI的操作顺序问题的相关文章

解决QT:forward declaration of &#39;struct Ui::xxx&#39;;invalid use of incomplete struct &quot;Ui::Widget&quot; 等莫名奇妙错误

今天在进行QT Widget的UI设计时,改了下Widget的对象名,然后在多次成功编译运行后,执行清理,重新构建,就出现了好多莫名奇妙的错误: widget.h:12: 错误:forward declaration of 'struct Ui::Widget' widget.cpp:8: 错误:invalid use of incomplete type 'struct Ui::Widget' 网上搜索发现是每当你新键一个 QT设计界面, QT会自动生成yyy.ui文件,如Widget.ui,

解决QT:forward declaration of &amp;#39;struct Ui::xxx&amp;#39;;invalid use of incomplete struct &amp;quot;Ui::Widget&amp;quot; 等莫名奇异错误

今天在进行QT Widget的UI设计时,改了下Widget的对象名,然后在多次成功编译执行后,执行清理,又一次构建,就出现了好多莫名奇异的错误: widget.h:12: 错误:forward declaration of 'struct Ui::Widget' widget.cpp:8: 错误:invalid use of incomplete type 'struct Ui::Widget' 网上搜索发现是每当你新键一个 QT设计界面, QT会自己主动生成yyy.ui文件,如Widget.

解决Qt程序在Linux下无法输入中文的办法

一位网友问我如何在Linux的Qt的应用程序中输入中文,我一开始觉得不是什么问题,但是后面自己尝试了一下还真不行.不仅是Qt制作的应用程序,就连Qt Creator都无法支持.后面看了一些资料,了解了Qt应用程序的方法,这里和大家分享一下. 写一个bash脚本,内容如下: #!/bin/sh cd YourBinaryDirectory export QT_IM_MODULE=iBus ./YourProjectBinary 如果想让Qt Creator也能输入中文,那么可以这么写: #!/bi

解决Qt中QTableWidget类方法setItem 时导致程序崩溃问题

在为一个音乐播放器增加功能时莫明奇妙的出现程序崩溃,定位到是由于QTableWidget 的setItem方法导致的,最终在此处找到了解决方式. 大致是说不能在setItem之前连接cellChanged 信号,把连接cellChanged信号的语句放置在一连串的setItem(在表格插入一行后调用的)之后就可以了. 解决Qt中QTableWidget类方法setItem 时导致程序崩溃问题

解决Qt Creator无法切换输入法(fcitx),不能录入汉字问题

系统环境,Ubuntu 14.04,输入法fcitx下搜狗输入法. 解决方法如下: 1.安装fcitx for Qt5动态库 sudo apt-get install fcitx-libs-qt5 2.向Qt Creator开发环境安装fcitx for Qt5支持 进入 /Qt5.3.1/Tools/QtCreator/bin/plugins/platforminputcontexts 目录发现 官网提供的安装包仅仅有libibusplatforminputcontextplugin.so,对

Qt Gui中父控件监听子控件的IO事件

父对象重新定义自己继承自QObject的函数bool eventFilter(QObject* watched, QEvent* event). 子控件安装父对象的eventFilter: 例如, QTableView * itsView = new QTableView; itsView->viewport()->installEventFilter(this); Qt Gui中父控件监听子控件的IO事件

用事件队列解决GUI的操作顺序问题(Qt中处理方法)

GUI操作顺序问题引发异常: 有时候我们使用写GUI程序的时候会遇到这样的问题:比如在程序中,建立了一个列表的GUI.这个列表是随着时间不断更新的,而且操作也会读取这个列表GUI的内容. 如果这个程序是多线程的程序,而且只是除了GUI的线程不操作,只是其他线程操作这个列表GUI,那么这个问题很简单,只用加互斥锁就可以了.但如果GUI线程自己本身也要操作这个列表,那么这个问题就很麻烦了. 我们可以很容易地想到一种场景,比如GUI线程读了列表的一些表项(比如选定),此时线程中的某个方法keep了这些

解决SAP740 GUI 搜索帮助(F4)回填值乱码的问题

SAP 740客户端引入了搜索帮助增强功能,并且默认是开启该功能的,在带有F4搜索帮助的字段输入框中输入字段的前两个字符,可以自动以下拉框的方式带出包含包含所输入字符的条目,从而实现快速的输入帮助,如下图所示: 但部分用户可能会遇到选中条目后回填到输入框中的值并非所需要的值,而是一串乱码,如下图所示: 出现这个问题的原因是SAP原生的740 GUI客户端存在BUG,解决这个问题的办法有两个: 方法1.关闭增强搜索功能 打开SAP登录器,选择左上角的文件夹图标,调出菜单栏,选择"选项",

解决Qt中文乱码以及汉字编码的问题(UTF-8/GBK)——ubuntu环境设置默认是utf-8,文件编码可使用Encodersoft批量转换

一.Qt环境设置 文件从window上传到Ubuntu后会显示乱码,原因是因为ubuntu环境设置默认是utf-8,Windows默认都是GBK.Windows环境下,Qt Creator,菜单->工具->选项->文本编辑器->行为->文件编码:默认编码:System(简体中文windows系统默认指的是GBK编码,即下拉框选项里的GBK/windows-936-2000/CP936/MS936/windows-936) 二.编码知识科普Qt常见的两种编码是:UTF-8和GB