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. [原创]内网渗透专用SSH连接工具sshcmd/sshshell/ssh密码破解以及Kali开启SSH

    目录 1.Kali开启SSH 2.SSH连接工具优缺点 3.渗透专用SSH连接工具 4.ssh执行cmd源码 5.批量SSH密码破解 6.相关工具下载 0x001 SSH配置 1.打开文件 etc/s ...

  2. nacos启动

    nacos下载 https://github.com/alibaba/nacos 1.执行数据库脚本 2.修改配置文件application.propertiesspring.datasource.p ...

  3. Elasticsearch and MongoDb

    http://www.linkedin.com/groups/Difference-between-elasticsearch-MongoDB-3393294.S.588764405916973056 ...

  4. Java反编译工具JD-GUI以及Eclipse的反编译插件

    什么是反编译 高级语言源程序经过编译变成可执行文件,反编译就是逆过程.但是通常不能把可执行文件变成高级语言源代码,只能转换成汇编程序. 反编译是一个复杂的过程,所以越是高级语言,就越难于反编译,但目前 ...

  5. springMVC 类型转换

    springMVC 类型转换 https://www.cnblogs.com/hafiz/p/5812873.html

  6. 解决web项目无法部署到eclipse配置的本地tomcat

    一.发现问题 在eclipse中新建Dynamic Web Project,配置好本地的tomcat并写好代码后选择Run on Server,但运行后发现在tomcat的安装目录下的webapps并 ...

  7. nutz 使用beetl

    src目录或src同级的其他目录(比如conf)下创建 beetl.properties文件,文件内容如下 (maven项目)在resources目录下创建 RESOURCE_LOADER=org.b ...

  8. js动态实现文本框不可编辑状态

    两种方法: $("#id").attr("readOnly",false); 不可编辑,可以传值 $("#id").attr("d ...

  9. base64模块

    ********base64模块******** Base64是一种用64个字符来表示任意二进制数据的方法. 用记事本打开exe.jpg.pdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很 ...

  10. 12.Maps

    1       Maps 1.1  Map声明和访问 maps中的元素是key-value对儿,key与value之间使用冒号(:)分割.创建一个空value的map,使用[:].默认情况下,map的 ...