用事件队列解决GUI的操作顺序问题(Qt中处理方法)
生活随笔
收集整理的這篇文章主要介紹了
用事件队列解决GUI的操作顺序问题(Qt中处理方法)
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
GUI操作順序問(wèn)題引發(fā)異常:
有時(shí)候我們使用寫(xiě)GUI程序的時(shí)候會(huì)遇到這樣的問(wèn)題:比如在程序中,建立了一個(gè)列表的GUI。這個(gè)列表是隨著時(shí)間不斷更新的,而且操作也會(huì)讀取這個(gè)列表GUI的內(nèi)容。 如果這個(gè)程序是多線(xiàn)程的程序,而且只是除了GUI的線(xiàn)程不操作,只是其他線(xiàn)程操作這個(gè)列表GUI,那么這個(gè)問(wèn)題很簡(jiǎn)單,只用加互斥鎖就可以了。但如果GUI線(xiàn)程自己本身也要操作這個(gè)列表,那么這個(gè)問(wèn)題就很麻煩了。 我們可以很容易地想到一種場(chǎng)景,比如GUI線(xiàn)程讀了列表的一些表項(xiàng)(比如選定),此時(shí)線(xiàn)程中的某個(gè)方法keep了這些表項(xiàng)的指針,然而此時(shí)很不幸別的線(xiàn)程有一個(gè)請(qǐng)求需要?jiǎng)h除列表中的一些表項(xiàng),并且這些表項(xiàng)有一些包含在了我們的選定內(nèi)容里,我們知道幾乎所有的語(yǔ)言操作GUI時(shí)都要進(jìn)入GUI線(xiàn)程里面操作,那么我們剛才選定表項(xiàng)的那個(gè)方法會(huì)被打斷,然后進(jìn)入刪除表項(xiàng)方法,在刪除了表項(xiàng)以后再次回到選定表項(xiàng)方法時(shí),我們的選定的表項(xiàng)有一些已經(jīng)被刪除了,此時(shí)我們?cè)龠M(jìn)行操作很有可能不符合我們的要求。 如果你是用一般是用C#,JAVA這種不用自己管理內(nèi)存的語(yǔ)言,那還好,只是結(jié)果可能不對(duì),但是如果是用C++這種需要我們自己管理內(nèi)存的來(lái)寫(xiě),很有可能我們會(huì)操作一個(gè)被釋放了內(nèi)存的對(duì)象,然后程序崩掉,這樣的情況是我們不想看到的。 ? 用事件隊(duì)列來(lái)解決問(wèn)題:
下面用一幅圖來(lái)表示如何設(shè)計(jì)事件隊(duì)列: 當(dāng)然圖中雖然是只有三種操作,如果你想,你可以設(shè)計(jì)出更多的操作,比如讀操作,你可以細(xì)分為復(fù)制表項(xiàng)中的信息和給表項(xiàng)中對(duì)應(yīng)的內(nèi)容進(jìn)行操作等。 這樣設(shè)計(jì)以后,就一定程度上消除了GUI打斷操作的問(wèn)題(比如我們會(huì)再遇到我們上面的那種訪(fǎng)問(wèn)了一個(gè)被析構(gòu)了的對(duì)象問(wèn)題)。 ? 在Qt中我們可以這樣寫(xiě):(ConnectionView這個(gè)對(duì)象就是我上面說(shuō)的那種表項(xiàng)) 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 };
有時(shí)候我們使用寫(xiě)GUI程序的時(shí)候會(huì)遇到這樣的問(wèn)題:比如在程序中,建立了一個(gè)列表的GUI。這個(gè)列表是隨著時(shí)間不斷更新的,而且操作也會(huì)讀取這個(gè)列表GUI的內(nèi)容。 如果這個(gè)程序是多線(xiàn)程的程序,而且只是除了GUI的線(xiàn)程不操作,只是其他線(xiàn)程操作這個(gè)列表GUI,那么這個(gè)問(wèn)題很簡(jiǎn)單,只用加互斥鎖就可以了。但如果GUI線(xiàn)程自己本身也要操作這個(gè)列表,那么這個(gè)問(wèn)題就很麻煩了。 我們可以很容易地想到一種場(chǎng)景,比如GUI線(xiàn)程讀了列表的一些表項(xiàng)(比如選定),此時(shí)線(xiàn)程中的某個(gè)方法keep了這些表項(xiàng)的指針,然而此時(shí)很不幸別的線(xiàn)程有一個(gè)請(qǐng)求需要?jiǎng)h除列表中的一些表項(xiàng),并且這些表項(xiàng)有一些包含在了我們的選定內(nèi)容里,我們知道幾乎所有的語(yǔ)言操作GUI時(shí)都要進(jìn)入GUI線(xiàn)程里面操作,那么我們剛才選定表項(xiàng)的那個(gè)方法會(huì)被打斷,然后進(jìn)入刪除表項(xiàng)方法,在刪除了表項(xiàng)以后再次回到選定表項(xiàng)方法時(shí),我們的選定的表項(xiàng)有一些已經(jīng)被刪除了,此時(shí)我們?cè)龠M(jìn)行操作很有可能不符合我們的要求。 如果你是用一般是用C#,JAVA這種不用自己管理內(nèi)存的語(yǔ)言,那還好,只是結(jié)果可能不對(duì),但是如果是用C++這種需要我們自己管理內(nèi)存的來(lái)寫(xiě),很有可能我們會(huì)操作一個(gè)被釋放了內(nèi)存的對(duì)象,然后程序崩掉,這樣的情況是我們不想看到的。 ? 用事件隊(duì)列來(lái)解決問(wèn)題:
下面用一幅圖來(lái)表示如何設(shè)計(jì)事件隊(duì)列: 當(dāng)然圖中雖然是只有三種操作,如果你想,你可以設(shè)計(jì)出更多的操作,比如讀操作,你可以細(xì)分為復(fù)制表項(xiàng)中的信息和給表項(xiàng)中對(duì)應(yīng)的內(nèi)容進(jìn)行操作等。 這樣設(shè)計(jì)以后,就一定程度上消除了GUI打斷操作的問(wèn)題(比如我們會(huì)再遇到我們上面的那種訪(fǎng)問(wèn)了一個(gè)被析構(gòu)了的對(duì)象問(wèn)題)。 ? 在Qt中我們可以這樣寫(xiě):(ConnectionView這個(gè)對(duì)象就是我上面說(shuō)的那種表項(xiàng)) 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 };
?
互斥鎖以及隊(duì)列的代碼: 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是一個(gè)消費(fèi)者線(xiàn)程,一直監(jiān)視著隊(duì)列的動(dòng)向,當(dāng)需要一個(gè)某個(gè)操作的時(shí)候,我們就可以引發(fā)一個(gè)對(duì)象操作的線(xiàn)程,把對(duì)應(yīng)操作加入隊(duì)列中(為什么需要開(kāi)一個(gè)線(xiàn)程,是為了方便互斥),比如下面我需要一個(gè)刪除操作: 刪除操作的代碼: 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 } 消費(fèi)者線(xiàn)程的代碼: 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在次線(xiàn)程上的 36 }?
這個(gè)時(shí)候我只需要在需要用到刪除操作的地方用: DeleteItemProducer *deleteItemProducer = new DeleteItemProducer(target,index); connect(deleteItemProducer, &QThread::finished, deleteItemProducer, &QThread::deleteLater); deleteItemProducer->start();?
啟動(dòng)刪除操作生產(chǎn)者的線(xiàn)程就可以了,我們就把刪除操作加入隊(duì)列中,合適的時(shí)候,消費(fèi)者線(xiàn)程會(huì)執(zhí)行這個(gè)操作,并且把操作投遞到GUI線(xiàn)程中進(jìn)行。 ? ?轉(zhuǎn)載于:https://www.cnblogs.com/Philip-Tell-Truth/p/6295186.html
總結(jié)
以上是生活随笔為你收集整理的用事件队列解决GUI的操作顺序问题(Qt中处理方法)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: hibernate-Maven
- 下一篇: 《图解服务器网络架构》 学习笔记