文中鼠标焦点泛指接收输入设备的事件,输入设备包括鼠标、键盘。
- 阻塞式任务是必须立即执行的任务。静默认任务是可以延缓执行的任务,它必须放在定时器中。
- 由于静默任务的hidden_ms内不处理SDL事件、不调用定时器,hidden_ms不能设得太长,最大2秒。
- 系统任一时刻最多存在一个任务。
有这么一个人员系统。首先发送“init”请求向服务器得到已有人员信息,然后每隔30秒左右发送“keepalive”向服务器获得人员库的最新版本,要是发现有变动了,发送“sync”下载人员变化部分,然合并到本地人员库。
- 阻塞式任务。到某个逻辑时,任务必须立即执行,只有执行出来结果后,逻辑才能继续。对执行时间,任务可能快很执行完毕,像不到一秒,也可能较长时间。当时间较长时,希望在界面显示个进度条。示例中的“init”、“sync”属于阻塞式任务。
- 静默任务。到某个逻辑时,可以执行任务了,但认为不满意的话,可以不立即执行,可以等稍后下一个时刻。对执行时间,和阻塞式任务一样,任务可能快很执行完毕,像不到一秒,也可能较长时间。当时间较长时,希望在界面显示个进度条。示例中“keepalive”属于静默认务。定时器超过30秒,可以发送了,如果不想,也可等到下一个定时器时刻,像30.01秒。
不论阻塞式任务还是静默式任务,都在主线程执行。在其它线程执行的任务称为后台任务、工作者任务,实现上用net::tworker。
类型 | api | 接管焦点情况 | 要求环境 |
阻塞式任务 | run_with_progress | 一旦运行,就要接管鼠标焦点。虽然hidden_ms时隐藏着进度条,那也要接管(注1) | 无限制 |
静默任务 | run_with_progress_quiet | 一开始hidden_ms毫秒不接收SDL事件,hidden_ms毫秒后才接管焦点(注2) | timer例程内(注3) |
- 注1:阻塞式任务中hidden_ms作用是指示在开始的hidden_ms毫秒内,不显示进度条。举个例子,要去服务器获取人员列表,hidden_ms设了3000,那获取操作如果在3秒内完成,界面不会显示进度条。假设没有hidden_ms参数,那只要操作一开始就有进度条,结果是一闪而过,这会让界面不友好。注意,阻塞式任务全程接管鼠标焦点,这和hidden_ms是什么值无关。
- 注2:静默任务中hidden_ms参考下面的“静默任务的hidden_ms毫秒”。
- 注4:要求静默认任务必须在timer内执行,是避免出一些意外。要求静默认任务的场合应该不多,而且是可以延时的,放在timer是个较好选择。
一、静默任务的hidden_ms毫秒
要完美的话,希望在这段时间里,不会影响界面处理,和平时一样接收并处理鼠标、键盘事件。想法很好,实际上很难做到。

- 静默任务极可能是chromium-http,像上面发“keepalive”。chromium-http不允许嵌套,也就是说,在执行过程中禁止产生新的chromium-http。于是会遇到无法处理的情况:在执行chromium-http静默任务,界面那边遇到要求立即产生新有chromium-http请求了。原因见图1,state_->run_depth只能是1,禁止嵌套。
- 虽然都是在主线程,静默任务执行的操作和界面产生操作是否真的能保证互不影响?
目前采用的是在这段时间内,1)不调用events::pump(),即不从SDL取走事件、不调用定时器,2)不刷新界面。
因为不取走SDL事件,这样hidden_ms内执行完的话,鼠标依旧可以保持着之前的跟随等状态,不会因为新来静默任务而中止拖拽。因为不调用定时器、刷新界面,界面会静止,像显示摄像头图像时。因为不能让压着太多SDL事件,以及界面长时间不动,设的hidden_ms不能太长,目前这个时间最长2秒。
void tprogress_widget::show_slice() { timer_handler(true); bool allow_handle_event = !quiet || float_track_.is_visible(); if (allow_handle_event) { twindow::tunique_event_widget_lock lock(window_, *float_track_.widget.get()); twidget::tdisable_popup_window_lock lock2("Must not popup new window during progress.show_slice()."); events::pump(); if (!float_track_.is_visible()) { absolute_draw(); } else { // why not use absolute_draw()? // --twindow::draw will call ttrack::did_draw_, did_draw_ may run_with_progress_widget or user's app logic, // and result dead-loop or exception. absolute_draw_float_widgets(); } } if (::instance != nullptr) { ::instance->app_ros_slice(); } // Add a delay so we don't keep spinning if there's no event. SDL_Delay(10); if (!require_cancel_ && ::instance->terminating()) { cancel_task(); } else if (float_track_.is_visible() && (last_frameTexture_size_.x != gui2::settings::screen_width || last_frameTexture_size_.y != gui2::settings::screen_height)) { set_track_rect(); } }
allow_handle_event要满足false,只一种情况,即正处在静默任务的hidden_ms毫秒。可以看到,在这时,不执行tunique_event_widget_lock(接管焦点)、events::pump(处理SDL事件、定时器),absolute_draw(刷新界面),但会调用::instance->app_ros_slice()。
二、先instance->pump,后gui2::pump_timers
namespace events { void pump() { ... if (!progress_running) { instance->pump(false); gui2::pump_timers(); } ... } }
为什么先执行instance->pump,然后是pump_timers。如果调换了,让看下面这个操作序列。
- 在这两语句外面的处理SDL事件过程中,产生了一个app_OnMessage方式的阻塞式任务。
- (gui2::pump_timers())判断出要新执行一个静默任务,由于当前没有任务,开始执行这个静默任务。
- (静默认任执行中,instance->pump)处理app_OnMessage投递来的阻塞式任务,但由于有静默任务正在执行,导致系统无法处理。
改为先instance->pump,然后pump_timers。
- 在这两语句外面的处理SDL事件过程中,产生了一个app_OnMessage方式的阻塞式任务。
- (instance->pump)处理app_OnMessage投递来的阻塞式任务。
- (阻塞式任务执行中,gui2::pump_timers())判断出要新执行一个静默任务,但由于有阻塞式任务正在执行,于是此次定时时刻不执行静默任务,等将来某个定时时刻阻塞式任务结束了,再执行这个静默任务。