• 用事件队列解决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线程中进行。
     
     
     
  • 相关阅读:
    C#高级编程第11版
    做点字符串题
    Codeforces Round #681 (Div. 1, based on VK Cup 2019-2020
    Educational Codeforces Round 97 题解
    AtCoder Regular Contest 106 题解
    Kick Start Round G 2020 题解
    CCSP 2020题解
    Codeforces Round #675 (Div. 2) 题解
    AtCoder Regular Contest 104
    Kick Start Round F 2020 题解
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/6295186.html
Copyright © 2020-2023  润新知