絕大多數事件回呼函數都會很快地執行完成,並不會造成對介面的假死。Nana庫的事件模型是對事件隊列的順序處理,這意味著當前一個事件處理函數完成之後才會調用下一個。
考慮下面的例子:
#include <nana/gui/wvl.hpp>#include <nana/gui/widgets/button.hpp>#include <nana/gui/widgets/progress.hpp>class example : public nana::gui::form{public: example() { btn_start_.create(*this, nana::rectangle(10, 10, 100, 20)); btn_start_.caption(STR("Start")); btn_start_.make_event<nana::gui::events::click>(*this, &example::_m_start); btn_cancel_.create(*this, nana::rectangle(120, 10, 100, 20)); btn_cancel_.caption(STR("Cancel")); btn_cancel_.make_event<nana::gui::events::click>(*this, &example::_m_cancel); prog_.create(*this, nana::rectangle(10, 40, 280, 20)); }private: void _m_start() { working_ = true; btn_start_.enabled(false); prog_.amount(100); for(int i = 0; i < 100 && working_; ++i) { nana::system::sleep(1000); //a long-running simulation prog_.value(i + 1); } btn_start_.enabled(true); } void _m_cancel() { working_ = false; }private: bool working_; nana::gui::button btn_start_; nana::gui::button btn_cancel_; nana::gui::progress prog_;};int main(){ example ex; ex.show(); nana::gui::exec(); return 0;}
這個簡單的程式類比了一個耗時的操作,一個Start按鈕和一個Cancel按鈕分別表示啟動和中止任務。不難想象,_m_start()會執行一個耗時的操作,當按下"Start"按鈕之後,會導致介面的假死。這時單擊“Cancel”也是沒有用的,因為對Start按鈕的事件處理還沒有結束,能做的只有等待。可以看到,即使釋放了滑鼠,按鈕還是按下的狀態,因為單擊事件還在處理中。
通常,解決這類耗時問題就是將耗時的處理過程放置到一個單獨的線程中,讓UI線程有閒置時間來響應使用者的操作,當耗時的操作完成,它就將結果返回給UI線程顯示。
考慮下面的方案:
#include <nana/gui/wvl.hpp>#include <nana/gui/widgets/button.hpp>#include <nana/gui/widgets/progress.hpp>#include <nana/threads/pool.hpp>class example : public nana::gui::form{public: example() { btn_start_.create(*this, nana::rectangle(10, 10, 100, 20)); btn_start_.caption(STR("Start")); btn_start_.make_event<nana::gui::events::click>(nana::threads::pool_push(pool_, *this, &example::_m_start)); btn_cancel_.create(*this, nana::rectangle(120, 10, 100, 20)); btn_cancel_.caption(STR("Cancel")); btn_cancel_.make_event<nana::gui::events::click>(*this, &example::_m_cancel); prog_.create(*this, nana::rectnagle(10, 40, 280, 20)); this->make_event<nana::gui::events::unload>(*this, &example::_m_cancel); }private: void _m_start() { working_ = true; btn_start_.enabled(false); prog_.amount(100); for(int i = 0; i < 100 && working_; ++i) { nana::system::sleep(1000); //a long-running simulation prog_.value(i + 1); } btn_start_.enabled(true); } void _m_cancel() { working_ = false; }private: volatile bool working_; nana::gui::button btn_start_; nana::gui::button btn_cancel_; nana::gui::progress prog_; nana::threads::pool pool_;};int main(){ example ex; ex.show(); nana::gui::exec(); return 0;}
Nana庫提供了一個線程池的類。解決這類問題時,使用線程池可以擺脫對線程的管理,例如,建立,等待和銷毀的問題。上面兩段代碼非常的相似,但是最主要的區別是_m_start() 被指派到線程池中並有線程池中,並有池中的線程處理, UI線程也因此不會被阻塞並有閒置時間來響應使用者操作。
這裡有一個叫pool_push()的函數,它建立一個pool_pusher函數對象用於把_m_start()函數推入線程池,換句話說,就是將pool_pusher函數對象當作了事件處理,當點擊Start按鈕時,pool_pusher會被調用,同時_m_start()則被推入到線程池中執行。
在該版本中,form對象註冊了一個unload事件並調用_m_cancel(),當關閉視窗的時候,程式放棄了剩餘的耗時操作。但是這裡有一個問題需要回答,當耗時操作還在工作的時候,關閉視窗會導致按鈕和進度條也同樣被銷毀,但是耗時操作並為結束,如果此時耗時操作訪問了按鈕和進度條對象是否會導致程式崩潰?回答是會崩潰,但是上面的代碼會避免在耗時操作完成之前銷毀按鈕和進度條,在類中,線程池的對象聲明在按鈕和進度條之後,這意味著線程池會在按鈕和進度條之前析構,當析構線程池的時候,它會等待所有的背景工作執行緒都已經結束。
在後台線程中處理阻塞操作
在某些情況下,耗時工作不能被取消,也不知道當前的進度。程式通常會用一直滾動的進度條表示正在處理。
#include <nana/gui/wvl.hpp>#include <nana/gui/widgets/button.hpp>#include <nana/gui/widgets/progress.hpp>#include <nana/threads/pool.hpp>class example : public nana::gui::form{public: example() { using namespace nana::gui; btn_start_.create(*this, nana::rectnagle(10, 10, 100, 20)); btn_start_.caption(STR("Start")); btn_start_.make_event<events::click>(nana::threads::pool_push(pool_, *this, &example::_m_start)); btn_start_.make_event<events::click>(nana::threads::pool_push(pool_, *this, &example::_m_ui_update)); prog_.create(*this, nana::rectangle(10, 40, 280, 20)); prog_.style(false); this->make_event<events::unload>(*this, &example::_m_cancel); }private: void _m_start() { btn_start_.enabled(false); nana::system::sleep(10000); //a blocking simulation btn_start_.enabled(true); } void _m_ui_update() { while(btn_start_.enabled() == false) { prog_.inc(); nana::system::sleep(100); } } void _m_cancel(const nana::gui::eventinfo& ei) { if(false == btn_start_.enabled()) ei.unload.cancel = true; }private: nana::gui::button btn_start_; nana::gui::progress prog_; nana::threads::pool pool_;};int main(){ example ex; ex.show(); nana::gui::exec(); return 0;}
單擊Start按鈕,程式會把_m_start()和_m_ui_update()都推入到線程池。是不是很容易?
歡迎各位提出意見,建議,並展開討論。