用事件隊列解決GUI的操作順序問題(Qt中處理方法)


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線程中進行。
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM