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. CodeForces722C Destroying Array【瞎搞】

    题意: 先给你一个序列,然后给你n个1-n的一个数,让你求前i个元素销毁的时候,区间字段和区间最大: 思路: 离线处理,维护新区间首尾位置的起点和终点,倒着处理: #include <bits/ ...

  2. Root Motion深度解析[Unity]

    http://blog.csdn.net/cubesky/article/details/39478207 在很多动画当中,模型的位置.角度往往会发生变化,我们需要决定是否将模型再动画中发生的这些变换 ...

  3. [Xcode 实际操作]九、实用进阶-(14)使用富文本CoreText框架创建丰富多彩的文本

    目录:[Swift]Xcode实际操作 本文将演示如何使用富文本CoreText框架创建丰富多彩的文本图形. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] imp ...

  4. 第四章 “我要点爆”微信小程序云开发之疯狂点击与糖果点爆页面制作

    疯狂点击点爆方式页面制作 疯狂点击为用户提供一个60秒的按钮点击时间,同时点击过程中有背景音乐,系统根据用户点击按钮的此时来进行热度值的计算. <view class="the_hea ...

  5. 第三章 “我要点爆”微信小程序云开发之点爆方式页面和爆炸之音页面制作

    点爆方式选择页面制作 在app.json中配置页面路径增加selectbao点爆方式选择,编译创建页面文件,点爆方式选择页面通过单选按钮组选择跳转到相应的点爆页面. <view class=&q ...

  6. mysql_innodb引擎

    innodb概括 1.Innodb是一种事务性存储引擎 2.完全支持事务的ACID特性 3.实现事务特性的原理: 使用Redo Log和Undo Log,Undo Log用于帮助未提交事务进行回滚,R ...

  7. python之错题巩固

    .#把班级学⽣数学考试成绩录⼊到⼀个列表中: 并求平均值. 要求: 录⼊的时候 # 要带着⼈名录⼊, 例如: 张三_44 li = [] : str_input = input('请输入你的姓名和分数 ...

  8. Hive_Hive的管理_web界面方式

    端口:9999启动方式: hive --service hwi &通过浏览器访问:http://<IP地址>:9999/hwi/ 执行启动命令后,报错,找不到hive-hwi-*. ...

  9. 如何查看和分析IIS日志

    日志的在IIS中是很重要的,但是很多人却忽略了,在这里说说,日志格式建议使用W3C扩充日志文件格式,这也是IIS 5.0默认的格式,可以指定每天记录客户IP地址.用户名.服务器端口.方法.URI资源. ...

  10. ASP.NET AJAX入门系列(5):使用UpdatePanel控件(二)

    UpdatePanel可以用来创建丰富的局部更新Web应用程序,它是ASP.NET 2.0 AJAX Extensions中很重要的一个控件,其强大之处在于不用编写任何客户端脚本,只要在一个页面上添加 ...