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) = ;
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();
启动删除操作生产者的线程就可以了,我们就把删除操作加入队列中,合适的时候,消费者线程会执行这个操作,并且把操作投递到GUI线程中进行。
 
 
 

用事件队列解决GUI的操作顺序问题(Qt中处理方法)的更多相关文章

  1. Qt中中文字符 一劳永逸的解决方法

    QT中中文字符问题,有没有一劳永逸的解决方法? 目前遇到有以下问题 1.字符串中有中文时,编译提示"常量中含有换行符" 2.在控制台窗口输出中文时无法正常显示,中文全部显示为? 目 ...

  2. Qt 中 Oracle 数据库 QOCI 驱动问题及解决

    Qt 中 Oracle 数据库 QOCI 驱动问题及解决是本文要讲述的问题,用Qt开发Oracle程序时,常会遇到QOCI驱动问题,主要表现为程序运行时出现下面的错误. QOCI driver not ...

  3. 快速解决Ubuntu/linux 环境下QT生成没有可执行文件(application/x-executable)

    快速解决Ubuntu/linux 环境下QT生成没有可执行文件(application/x-executable)(转载)   问题描述 与windows环境下不同,linux选择debug构建时并不 ...

  4. Windows平台下Qt QOpenGL中glutSolidSphere()方法未定义的解决方法

    Windows平台下Qt中glut库的使用     用Qt中的QGLWidget窗体类中是不包括glut工具库的,难怪在myGLWidget(在我的程序中是QGLWidget的派生类)中绘制实心球体是 ...

  5. 【Qt开发】qt中涉及到空格包含路径的解决办法

    qt中涉及到空格路径,qmake是无法正确编译的. 需要在空格路径前面加上$$quote INCLUDEPATH += $$quote(C:/Program Files/MySQL/MySQL Ser ...

  6. QT中QString与string的转化,解决中文乱码问题

    在QT中,使用QString输出到控件进行显示时,经常会出现中文乱码,网上查了一圈,发现大部分都是针对QT4增加4条语句:</span> [cpp] view plain copy QTe ...

  7. Qt入门(9)——Qt中的线程支持

    Qt对线程提供了支持,基本形式有独立于平台的线程类.线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法.警告:所有的GUI类(比如,QWidget和它的子类),操作系统核心 ...

  8. Qt中调用PolarSSL库(一)

    最近一直在学习SSL相关的知识,也是先了解理论相关的知识,主要是SSL相关的基本概念和连接建立过程,主要是基于PolarSSL开源库进行学习.学习完了之后就希望能给有所运用,就想用Qt写一个简单的程序 ...

  9. Qt中Ui名字空间以及setupUi函数的原理和实现 <转>

    用最新的QtCreator选择GUI的应用会产生含有如下文件的工程 下面就简单分析下各部分的功能. .pro文件是供qmake使用的文件,不是本文的重点[不过其实也很简单的],在此不多赘述. 所以呢, ...

随机推荐

  1. setInterval和setTImeout中的this指向问题

    问题:在setInterval和setTimeout中传入函数时,函数中的this会指向window对象 解决方法: 1. 将当前对象的this存为一个变量,定时器内的函数利用闭包来访问这个变量.va ...

  2. [Xcode 实际操作]八、网络与多线程-(15)使用网址会话对象URLSession下载图片并存储在沙箱目录中

    目录:[Swift]Xcode实际操作 本文将演示如何通过网址会话对象URLSession下载图片并存储在沙箱目录中. 网址会话对象URLSession具有在后台上传和下载.暂停和恢复网络操作.丰富的 ...

  3. boot接入elasticsearch

    boot接入elasticsearch 参考博客:https://blog.csdn.net/li521wang/article/details/83792552 项目源码demo:https://g ...

  4. bzoj1458士兵占领

    传送门 和上一题差不多,每行和每列分别看做一个点,障碍点坐标的行和列就不建边,再按照有源汇上下界建图就好了,唯一的区别就是这个题求的是最小流 这个题的数据好水呢,建错图也能A呢 #include< ...

  5. PAT甲级——1130 Infix Expression (25 分)

    1130 Infix Expression (25 分)(找规律.中序遍历) 我是先在CSDN上面发表的这篇文章https://blog.csdn.net/weixin_44385565/articl ...

  6. Gym - 101810B ACM International Collegiate Programming Contest (2018)

    bryce1010模板 http://codeforces.com/gym/101810 #include <bits/stdc++.h> using namespace std; #de ...

  7. Android 流量测试方法

    流量测试怎么测?下面总结了几个方法: 一.通过pid获取流量 > 1.获取应用的pid adb shell ps | grep packagename 2.通过pid获取该进程的流量信息 adb ...

  8. HDU4405 Aeroplane chess(期望dp)

    题意 抄袭自https://www.cnblogs.com/Paul-Guderian/p/7624039.html 正在玩飞行棋.输入n,m表示飞行棋有n个格子,有m个飞行点,然后输入m对u,v表示 ...

  9. ES-Mac OS环境搭建-ik中文分词器

    下载 从github下载ik中文分词器,点击地址,需要注意的是,ik分词器和elasticsearch版本必须一致. 安装 下载到本地并解压到elasticsearch中的plugins目录内即可. ...

  10. 从零开始利用vue-cli搭建简单音乐网站(二)

    1.利用vue-router实现页面跳转 程序可以正常运行之后,下面我们需要配置路由实现页面的局部刷新,这一功能将用来实现网站页面的跳转. 打开程序目录,进入"src\router\inde ...