Nana开发:防止耗时处理导致界面的阻塞

Jinhao 2011-11-01 06:58:04
加精
降低多线程开发的难度是Nana C++ Library的设计目标。
项目网页地址
介绍贴

绝大多数事件回调函数都会很快地执行完成,并不会造成对界面的假死。Nana库的事件模型是对事件队列的顺序处理,这意味着当前一个事件处理函数完成之后才会调用下一个。
考虑下面的例子:

#include <nana/gui/wvl.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/progressbar.hpp>

class example
: public nana::gui::form
{
public:
example()
{
btn_start_.create(*this, 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, 120, 10, 100, 20);
btn_cancel_.caption(STR("Cancel"));
btn_cancel_.make_event<nana::gui::events::click>(*this, &example::_m_cancel);
prog_.create(*this, 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::progressbar 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/progressbar.hpp>
#include <nana/threads/pool.hpp>

class example
: public nana::gui::form
{
public:
example()
{
btn_start_.create(*this, 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, 120, 10, 100, 20);
btn_cancel_.caption(STR("Cancel"));
btn_cancel_.make_event<nana::gui::events::click>(*this, &example::_m_cancel);
prog_.create(*this, 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::progressbar 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/progressbar.hpp>
#include <nana/threads/pool.hpp>

class example
: public nana::gui::form
{
public:
example()
{
using namespace nana::gui;

btn_start_.create(*this, 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, 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::progressbar prog_;
nana::threads::pool pool_;
};

int main()
{
example ex;
ex.show();
nana::gui::exec();
return 0;
}

http://stdex.sourceforge.net/res/blocking_ui_03.png
单击Start按钮,程序会把_m_start()和_m_ui_update()都推入到线程池。是不是很容易?
欢迎各位提出意见,建议,并展开讨论。
如果该贴已沉且有疑问的朋友,可以去我的blog留言。
...全文
1251 点赞 收藏 68
写回复
68 条回复
向立天 2011年12月05日
您好
我是本版版主
此帖已多日无人关注
请您及时结帖
如您认为问题没有解决可按无满意结帖处理
另外本版设置了疑难问题汇总帖
并已在版面置顶
相关规定其帖子中有说明
您可以根据规定提交您帖子的链接
如您目前不想结帖只需回帖说明
我们会删除此结帖通知

见此回复三日内无回应
我们将强制结帖
相关规定详见界面界面版关于版主结帖工作的具体办法
回复 点赞
程序员小哈 2011年11月06日
回复 点赞
Jinhao 2011年11月05日
欢迎观看。
如果有兴趣尝试用Nana库开发程序的朋友,可以看看我的blog,专门开了一个分类用来讲解。
分类Blog地址
回复 点赞
e_yxc 2011年11月05日
真的是不太懂。。。
回复 点赞
wjlazio 2011年11月04日
看看,学习了。。。。
回复 点赞
alpha.5 2011年11月04日
好东西.. 支持
回复 点赞
beautifulcool_9686 2011年11月04日
顶一下,确实不错!
回复 点赞
gdcrk 2011年11月04日
看不懂啊!杯具!
回复 点赞
Jinhao 2011年11月04日
[Quote=引用 51 楼 rangerlee 的回复:]

支持下,文档都很落后了,和代码都不同步,玩了下还是很不错的,有些地方都不太懂怎么搞,跟LZ学学
[/Quote]

文档我都跟着的,应该算是文档的BUG。
回复 点赞
rangerlee 2011年11月04日
支持下,文档都很落后了,和代码都不同步,玩了下还是很不错的,有些地方都不太懂怎么搞,跟LZ学学
回复 点赞
tubo_true 2011年11月03日
不错~
回复 点赞
Eleven 2011年11月03日
不错~
回复 点赞
xiaocongzhi 2011年11月03日
Awesome,楼主 威武
回复 点赞
rendao0563 2011年11月03日
UI 自己整 不如去跟着Qt整了. 自己倒腾倒腾学习倒是不错.
回复 点赞
healer_kx 2011年11月02日
师父,老实说,跟你Q过后,我觉得Nana可能有跨平台的使命,但是可能有些时候,效率不高。

不过这是可能是所有跨平台代码的问题,没法很好的利用某个特定平台的优势。

特别是UI相关的代码,是非常复杂的,我只有向你致敬啦~~~
回复 点赞
wjlazio 2011年11月02日
学习了。。。。
回复 点赞
lyh7736362 2011年11月02日
降低多线程开发的难度是Nana C++ Library的设计目标。
项目网页地址
回复 点赞
andyypc 2011年11月02日
好东西,谢谢
回复 点赞
detecyang 2011年11月02日
界面是怎么写的,不过因为这库的名字 不会考虑去用它
回复 点赞
lijianli9 2011年11月02日
bu cuo
回复 点赞
发动态
发帖子
界面
创建于2007-09-28

7973

社区成员

11.5w+

社区内容

VC/MFC 界面
社区公告
暂无公告