用事件队列解决GUI的操作顺序问题(Qt中处理方法)
2017-01-18 00:01
645 查看
GUI操作顺序问题引发异常:
有时候我们使用写GUI程序的时候会遇到这样的问题:比如在程序中,建立了一个列表的GUI。这个列表是随着时间不断更新的,而且操作也会读取这个列表GUI的内容。
如果这个程序是多线程的程序,而且只是除了GUI的线程不操作,只是其他线程操作这个列表GUI,那么这个问题很简单,只用加互斥锁就可以了。但如果GUI线程自己本身也要操作这个列表,那么这个问题就很麻烦了。
我们可以很容易地想到一种场景,比如GUI线程读了列表的一些表项(比如选定),此时线程中的某个方法keep了这些表项的指针,然而此时很不幸别的线程有一个请求需要删除列表中的一些表项,并且这些表项有一些包含在了我们的选定内容里,我们知道几乎所有的语言操作GUI时都要进入GUI线程里面操作,那么我们刚才选定表项的那个方法会被打断,然后进入删除表项方法,在删除了表项以后再次回到选定表项方法时,我们的选定的表项有一些已经被删除了,此时我们再进行操作很有可能不符合我们的要求。
如果你是用一般是用C#,JAVA这种不用自己管理内存的语言,那还好,只是结果可能不对,但是如果是用C++这种需要我们自己管理内存的来写,很有可能我们会操作一个被释放了内存的对象,然后程序崩掉,这样的情况是我们不想看到的。
用事件队列来解决问题:
下面用一幅图来表示如何设计事件队列:
当然图中虽然是只有三种操作,如果你想,你可以设计出更多的操作,比如读操作,你可以细分为复制表项中的信息和给表项中对应的内容进行操作等。
这样设计以后,就一定程度上消除了GUI打断操作的问题(比如我们会再遇到我们上面的那种访问了一个被析构了的对象问题)。
在Qt中我们可以这样写:(ConnectionView这个对象就是我上面说的那种表项)
互斥锁以及队列的代码:
ConsumerHelper是一个消费者线程,一直监视着队列的动向,当需要一个某个操作的时候,我们就可以引发一个对象操作的线程,把对应操作加入队列中(为什么需要开一个线程,是为了方便互斥),比如下面我需要一个删除操作:
删除操作的代码:
消费者线程的代码:
这个时候我只需要在需要用到删除操作的地方用:
启动删除操作生产者的线程就可以了,我们就把删除操作加入队列中,合适的时候,消费者线程会执行这个操作,并且把操作投递到GUI线程中进行。
有时候我们使用写GUI程序的时候会遇到这样的问题:比如在程序中,建立了一个列表的GUI。这个列表是随着时间不断更新的,而且操作也会读取这个列表GUI的内容。
如果这个程序是多线程的程序,而且只是除了GUI的线程不操作,只是其他线程操作这个列表GUI,那么这个问题很简单,只用加互斥锁就可以了。但如果GUI线程自己本身也要操作这个列表,那么这个问题就很麻烦了。
我们可以很容易地想到一种场景,比如GUI线程读了列表的一些表项(比如选定),此时线程中的某个方法keep了这些表项的指针,然而此时很不幸别的线程有一个请求需要删除列表中的一些表项,并且这些表项有一些包含在了我们的选定内容里,我们知道几乎所有的语言操作GUI时都要进入GUI线程里面操作,那么我们刚才选定表项的那个方法会被打断,然后进入删除表项方法,在删除了表项以后再次回到选定表项方法时,我们的选定的表项有一些已经被删除了,此时我们再进行操作很有可能不符合我们的要求。
如果你是用一般是用C#,JAVA这种不用自己管理内存的语言,那还好,只是结果可能不对,但是如果是用C++这种需要我们自己管理内存的来写,很有可能我们会操作一个被释放了内存的对象,然后程序崩掉,这样的情况是我们不想看到的。
用事件队列来解决问题:
下面用一幅图来表示如何设计事件队列:
当然图中虽然是只有三种操作,如果你想,你可以设计出更多的操作,比如读操作,你可以细分为复制表项中的信息和给表项中对应的内容进行操作等。
这样设计以后,就一定程度上消除了GUI打断操作的问题(比如我们会再遇到我们上面的那种访问了一个被析构了的对象问题)。
在Qt中我们可以这样写:(ConnectionView这个对象就是我上面说的那种表项)
1 class ItemsOpsBase 2 { 3 public: 4 virtual void doOperation(ConnectionView *view) = 0; 5 virtual ~ItemsOpsBase() = default; 6 }; 7 8 class DeleteItem : public ItemsOpsBase 9 { 10 public: 11 DeleteItem(qintptr target,qint32 connectionIndex) 12 :ItemsOpsBase(), _target(target),_connectionIndex(connectionIndex){ } 13 14 void doOperation(ConnectionView *view)override; 15 ~DeleteItem() = default; 16 private: 17 qintptr _target; 18 qint32 _connectionIndex; 19 }; 20 21 class UpdatePulse :public ItemsOpsBase 22 { 23 public: 24 UpdatePulse(qintptr descriptor,qint32 currentTime) 25 :ItemsOpsBase(), _descriptor(descriptor),_currentTime(currentTime){ } 26 27 void doOperation(ConnectionView *view)override; 28 ~UpdatePulse() = default; 29 private: 30 qintptr _descriptor; 31 qint32 _currentTime; 32 }; 33 34 class UpdateRemark : public ItemsOpsBase 35 { 36 public: 37 UpdateRemark(qintptr descriptor, const QString &remark) 38 : ItemsOpsBase(),_remark(remark),_descriptor(descriptor){ } 39 40 void doOperation(ConnectionView *view)override; 41 ~UpdateRemark() = default; 42 private: 43 QString _remark; 44 qintptr _descriptor; 45 }; 46 47 class TestConnection : public ItemsOpsBase 48 { 49 public: 50 void doOperation(ConnectionView *view)override; 51 }; 52 class TestConnectionProducer : public QThread 53 { 54 public: 55 void run()override; 56 }; 57 58 class CopySelectedItemInformProducer : public QThread 59 { 60 public: 61 void run()override; 62 }; 63 64 class DisconnectTargetsProducer : public QThread 65 { 66 public: 67 void run()override; 68 }; 69 70 class DeleteItemProducer :public QThread 71 { 72 public: 73 DeleteItemProducer(qintptr target, qint32 connectionIndex) 74 : QThread(),_target(target),_connectionIndex(connectionIndex) { } 75 void run()override; 76 private: 77 qintptr _target; 78 qint32 _connectionIndex; 79 }; 80 81 class UpdatePulseProducer :public QThread 82 { 83 public: 84 UpdatePulseProducer(qintptr descriptor, qint32 currentTime) 85 :QThread(),_descriptor(descriptor),_currentTime(currentTime){ } 86 protected: 87 void run()override; 88 private: 89 qintptr _descriptor; 90 qint32 _currentTime; 91 }; 92 93 class UpdateRemarkProducer : public QThread 94 { 95 public: 96 UpdateRemarkProducer(qintptr descriptor, const QString &remark) 97 :QThread(),_remark(remark),_descriptor(descriptor){ } 98 protected: 99 void run()override; 100 private: 101 QString _remark; 102 qintptr _descriptor; 103 }; 104 class ConsumerHelper :public QThread 105 { 106 public: 107 ConsumerHelper(ConnectionView *view) 108 :QThread(),_view(view){ } 109 ~ConsumerHelper(); 110 protected: 111 void run() override; 112 private: 113 ConnectionView *_view; 114 115 ConsumerHelper(const ConsumerHelper &other) = delete; 116 ConsumerHelper(const ConsumerHelper &&other) = delete; 117 ConsumerHelper &operator=(const ConsumerHelper &other) = delete; 118 };
互斥锁以及队列的代码:
1 static QQueue<QSharedPointer<ItemsOpsBase>> &opQueue() 2 { 3 static QQueue<QSharedPointer<ItemsOpsBase>> queue; 4 return queue; 5 } 6 7 static QSharedPointer<ItemsOpsBase> endOperation; 8 9 static QMutex &opQueueLock() 10 { 11 static QMutex mutex; 12 return mutex; 13 } 14 static QWaitCondition &opQueueIsAvailable() 15 { 16 static QWaitCondition flag; 17 return flag; 18 }
ConsumerHelper是一个消费者线程,一直监视着队列的动向,当需要一个某个操作的时候,我们就可以引发一个对象操作的线程,把对应操作加入队列中(为什么需要开一个线程,是为了方便互斥),比如下面我需要一个删除操作:
删除操作的代码:
1 void DeleteItem::doOperation(ConnectionView *view) 2 { 3 qRegisterMetaType<qintptr>("qintptr"); 4 qRegisterMetaType<TcpConnectionHandler *>("TcpConnectionHandler *"); 5 QMetaObject::invokeMethod(view, "deleteConnection",Qt::QueuedConnection, Q_ARG(qintptr, _target), Q_ARG(qint32, _connectionIndex)); 6 } 7 void DeleteItemProducer::run() 8 { 9 QSharedPointer<ItemsOpsBase> op = QSharedPointer<ItemsOpsBase>(new DeleteItem(_target,_connectionIndex)); 10 11 QMutexLocker locker(&opQueueLock()); 12 opQueue().enqueue(op); 13 opQueueIsAvailable().wakeOne(); 14 }
消费者线程的代码:
1 void ConsumerHelper::run() 2 { 3 forever 4 { 5 QSharedPointer<ItemsOpsBase> opPointer; 6 7 { 8 QMutexLocker locker(&opQueueLock()); 9 10 if (opQueue().isEmpty()) 11 opQueueIsAvailable().wait(&opQueueLock()); 12 opPointer = opQueue().dequeue(); 13 14 if (opPointer == endOperation) 15 break; 16 } 17 { 18 if(!opPointer.isNull()) 19 opPointer->doOperation(_view); 20 } 21 } 22 } 23 24 ConsumerHelper::~ConsumerHelper() 25 { 26 { 27 QMutexLocker locker(&opQueueLock()); 28 while(!opQueue().isEmpty()) 29 opQueue().dequeue(); 30 31 opQueue().enqueue(endOperation); 32 opQueueIsAvailable().wakeOne(); 33 } 34 35 wait();//注意这里是wait在次线程上的 36 }
这个时候我只需要在需要用到删除操作的地方用:
DeleteItemProducer *deleteItemProducer = new DeleteItemProducer(target,index); connect(deleteItemProducer, &QThread::finished, deleteItemProducer, &QThread::deleteLater); deleteItemProducer->start();
启动删除操作生产者的线程就可以了,我们就把删除操作加入队列中,合适的时候,消费者线程会执行这个操作,并且把操作投递到GUI线程中进行。
相关文章推荐
- Qt4项目迁移Qt5项目一问题解决方法:#include <QtGui/QApplication> ---> No such file or directory
- js学习总结之DOM2兼容处理顺序问题的解决方法
- Qt4项目迁移Qt5项目一问题解决方法:#include <QtGui/QApplication> ---> No such file or directory
- 要求顺序循环队不损失一个空间,全部能够得到有效利用,试采用设置标志位tag的方法解决“假溢出”问题,实现顺序循环队列算法
- poi操作excel文件的两个小问题解决方法
- sqlite3 jdbc、c接口、python接口处理中文时遇到的问题及其解决方法
- 关于Cookie跨域操作遇到的问题及解决方法
- 解决大量TCPIP连接后出现“因为系统缺乏足够缓冲区空间或者因为队列已满无法执行套接字上操作”的问题
- 关于FLEX事件堆积 导致处理操作叠加 问题
- "服务器存储空间不足,无法处理此命令"问题解决方法
- 钩子中向窗口发送消息、操作窗口无反应的问题解决方法(Hook dll ShowWindow HWND)
- Cookie跨域操作问题及解决方法(asp)
- sqlite3 jdbc、c接口、python接口处理中文时遇到的问题及其解决方法
- 关于Cookie跨域操作遇到的问题及解决方法
- 动态加载JS文件,完美解决跨域、编码、嵌套、队列、兼容性、执行顺序等相关问题。
- 触发器(当2个表中的相应值改变时同时改变一个表中的一个字段)(同时有处理“无法解决 equal to 操作的排序规则冲突”问题)
- 解决鼠标操作代理事件在多个程序集中出现的问题
- 编译QT/E应用程序时发生的多重定义问题和解决方法
- 在workflow中,无法为实例 ID“...”传递接口类型“...”上的事件“...” 问题的解决方法。
- Qt中事件处理的顺序