您的位置:首页 > 产品设计 > UI/UE

用事件队列解决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这个对象就是我上面说的那种表项)

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线程中进行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐