.net中的委托—事件机制: 办公室的故事
chris sells 著 ( <<atl internals>>一书作者之一,该书是atl编程的宝典)
jackeygou 译 研发中心
软件技术的动人美感来源于对现实世界的真实理解. 一— 译注
===========================================
强类型耦合
————
从前在我们这个城市的西南角,有一家小技术服务公司,公司里有一位聪明能干的年轻人,他的名字叫peter。不幸的是他的老板却是一位吝啬、多疑,而且极为循规蹈矩的小人,例如下属的任何工作都必须先报告,而且经他审批后才能进行。可怜的peter自然不愿他的老板整日里站在自己的身后虎视眈眈,于是他对老板保证,自己的任何工作进度都会向他及时通禀。peter实现这一承诺的方法就是周期性的利用类型引用回调boss,把他老板叫过来审查。程序实现如下:
class worker {
public void advise(boss boss) { _boss = boss; }
public void dowork() {
console.writeline("worker: work started");
if( _boss != null ) _boss.workstarted(); // 开始工作的审批
console.writeline("worker: work progressing");
if( _boss != null ) _boss.workprogressing(); // 进行工作的审批
console.writeline("worker: work completed");
if( _boss != null ) {
int grade = _boss.workcompleted(); // 完成工作的审批
console.writeline("worker grade= " + grade);
}
}
private boss _boss;
}
class boss {
public void workstarted() { /* 老板实际上并不很关心. */ }
public void workprogressing() { /*老板实际上并不很关心. */ }
public int workcompleted() {
console.writeline("its about time!");
return 2; /* 满分10分,才给2分,够吝啬小气吧. */
}
}
class universe {
static void main() {
worker peter = new worker(); // 生成peter实例
boss boss = new boss(); // 生成boss实例
peter.advise(boss);
peter.dowork();
console.writeline("main: 工作结束!");
console.readline();
}
}
【译注:以下是上段程序输出结果:
worker: work started
worker: work progressing
worker: work completed
its about time!
worker grade = 2
main: worker completed work
】
接口
———-
现在peter已经成为一个特殊的成员,因为它不仅要忍受它那位吝啬老板的指使,而且还与universe对象紧密相关(没办法谁让他身不逢时处于universe类的main函数中)。这种“亲密接触”使peter觉得universe对他的工作进度也很感兴趣。不幸的是,除了保证boss能够被通知外,如果不为universe添加一个特殊的通知方法和回调,peter无法向universe通知其工作进度。而peter首先要做的就是从具体的通知执行过程中分离出隐藏的通知约定,而这就需要定义接口了:
interface iworkerevents {
void workstarted();
void workprogressing();
int workcompleted();
}
class worker {
public void advise(iworkerevents events) { _events = events; }
public void dowork() {
console.writeline("worker: work started");
if( _events != null ) _events.workstarted();
console.writeline("worker: work progressing");
if(_events != null ) _events.workprogressing();
console.writeline("worker: work completed");
if(_events != null ) {
int grade = _events.workcompleted();
console.writeline("worker grade= " + grade);
}
}
private iworkerevents _events;
}
class boss : iworkerevents {
public void workstarted() { /* boss doesnt care. */ }
public void workprogressing() { /* boss doesnt care. */ }
public int workcompleted() {
console.writeline("its about time!");
return 3; /* out of 10 */
}
}
【译注:以下是上段程序输出结果:
worker: work started
worker: work progressing
worker: work completed
its about time!
worker grade = 3
main: worker completed work
】
委托
———–
不幸的是,peter整日忙于通知他的老板去负责执行接口,而无暇顾及通知universe。但是peter觉得这已经不远了,因为至少他已经成功的将对具体老板的引用,抽象为对统一的iworkerevents接口的引用了。换句话说,别的实现了iworkerevents接口的什么人都可以收到工作进度通知。
就这样,过了一段时间,peter的老板依然对peter很不满,他大声的抱怨到:“hi! peter,你知道不知道,你真的很烦呀!为什么你每次在开始工作和工作进行时都要叫上我,我并不是每次都对这些过程感兴趣。你不仅强迫我做一些我不感兴趣的事情,而且你还浪费了大量宝贵的工作时间在等我从审批事件中返回。如果我很忙,不能做出回答,难道你就要放假不成?你能不能够别这样来打搅我,ok?”
此时,peter逐渐意识到采用接口在很多时候时是非常有用的(译注:接口在com世界里,可以称得上是万物之本),但是用接口来处理事件时,就有些粒度(译注:不是力度)不够精细。他希望在事件发生时只通知哪些对该事件感兴趣的人,而不是让一个人必须对所有的事件感兴趣。所以,peter将接口进一步肢解成更小的独立的委托函数,每一个委托函数可以看作是一个轻量级的函数接口,意义等价于类接口。
delegate void workstarted();
delegate void workprogressing();
delegate int workcompleted();
class worker {
public void dowork() {
console.writeline("worker: work started");
if( started != null ) started();
console.writeline("worker: work progressing");
if( progressing != null ) progressing();
console.writeline("worker: work completed");
if( completed != null ) {
int grade = completed();
console.writeline("worker grade= " + grade);
}
}
public workstarted started;
public workprogressing progressing;
public workcompleted completed;
}
class boss {
public int workcompleted() {
console.writeline("better…");
return 4; /* out of 10 */
}
}
class universe {
static void main() {
worker peter = new worker();
boss boss = new boss();
peter.completed = new workcompleted(boss.workcompleted);
peter.dowork();
console.writeline("main: worker completed work");
console.readline();
}
}
【译注:以下是上段程序输出结果:
worker: work started
worker: work progressing
worker: work completed
better…
worker grade = 4
main: worker completed work
】
【译注:对“但是用接口来处理事件时,就有些粒度不够精细。”的理解可用下例说明,请仔细观察一下程序,如果全部换作接口定义去处理,思考一下这样做的不利之处。欢迎大家讨论:
using system;
interface iworkstartedevent
{
void workstarted();
}
interface iworkprogressingevent
{
void workprogressing();
}
interface iworkcompletedevent
{
int workcompleted();
}
class worker
{
public void advise(iworkcompletedevent aevent)
{
_event = aevent;
}
public void dowork()
{
console.writeline("worker: work completed");
if(_event != null )
{
int grade = _event.workcompleted();
console.writeline("worker grade = " + grade);
}
}
private iworkcompletedevent _event;
}
class boss : iworkcompletedevent
{
public int workcompleted()
{
console.writeline("better…");
return 4; /* out of 10 */
}
}
class universe
{
static void main()
{
worker peter = new worker();
boss boss = new boss();
peter.advise(boss);
peter.dowork();
console.writeline("main: worker completed work");
console.readline();
}
}
以下是上段程序输出结果:
worker: work completed
better…
worker grade = 4
main: worker completed work
】
静态响应函数
—————
这样一来,果然立竿见影。peter再也不会因为开始工作审批等事件而去打搅他的老板。但是现在的问题是peter依然没有将universe列入他的事件响应者名单。因为universe是一个全封闭实体类(all-compassing entity),所以,如果需要与委托相连,就要实例化universe,实在没必要(设想一下universe的多个实例需要多少资源…)。取而代之,采用静态函数实现。因为委托支持这些静态函数定义:
class universe {
static void workerstartedwork() {
console.writeline("universe notices worker starting work");
}
static int workercompletedwork() {
console.writeline("universe pleased with workers work");
return 7;
}
static void main() {
worker peter = new worker();
boss boss = new boss();
peter.completed = new workcompleted(boss.workcompleted);// #
peter.started = new workstarted(universe.workerstartedwork);
peter.completed = new workcompleted(universe.workercompletedwork); // 这一行使得前面#行白费了!boss被universe取代了,也许peter更愿意如此。
peter.dowork();
console.writeline("main: worker completed work");
console.readline();
}
}
【译注:以下是上段程序输出结果:
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
universe pleased with workers work
worker grade = 7
main: worker completed work
】
事件
———-
事与愿违,虽然peter很乐意让universe参与合作,但universe自身很忙也不习惯将自己的全部身心都投入到单一的peter实例中,以取代peter老板委托的位置。此外peter类中的委托字段是公有访问权限也会存在一些未知的负面作用。例如,那天peter的老板想不开,突然决定要亲自激活peter的委托任务,也尚未可知。(按此人的多疑易怒的性情是很有可能的!)
// peter的老板自己将委托任务激活,这就是公有权限的恶果!!!
if( peter.completed != null ) peter.completed();
可怜的peter自然不愿意上述的任何一种情况发生,他需要实现对于每一个委托都能加入注册和反注册函数,只有这样事件响应者可以添加和删除自身, 但谁都不能够清空整个事件列表,避免出现刚才universe将boss取而代之的结果。peter自身对此是无能为力了,但是通过c#提供的“event”关键字,peter就可以梦想成真了:
class worker {
…
public event workstarted started;
public event workprogressing progressing;
public event workcompleted completed;
}
peter知道利用c#的委托—事件机制可以轻松搞定,而且这时event关键字提供了一个关于委托的属性(property),可以允许c#客户方便地通过“+=”和“-= ”添加和删除他们自定义的执行函数,而不像委托那样挂上就甩不掉:
static void main() {
worker peter = new worker();
boss boss = new boss();
peter.completed += new workcompleted(boss.workcompleted);
peter.started += new workstarted(universe.workerstartedwork);
peter.completed += new workcompleted(universe.workercompletedwork);
peter.dowork();
console.writeline("main: worker completed work");
console.readline();
}
【译注:以下是上段程序输出结果
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
better…// 【译注:boss也通知到啦,刚才打‘#’那代码被执行了。但是且慢,boss打的那4分没有得到,后面只得到了universe给的7分】
universe pleased with workers work
worker grade = 7
main: worker completed work
】
查询所有结果
————–
对于这一点,peter满怀信心。他可以方便管理所有与事件相关的执行者信息,而不需要与具体的执行者产生紧耦合关系。peter注意到他的最终工作评定(completed事件)关联了两个事件执行函数:boss和universe。他们都给peter一个评定分数4分和7分。正是春风得意的peter自然不会随意丢掉任何一个结果,他希望能得到每一个响应者的评分结果。因此,他决定提取委托调用列表,以便手工分别调用它们并累加,这样他的money….
public void dowork() {
…
console.writeline("worker: work completed");
int grade = 0;
if( completed != null ) {
foreach( workcompleted wc in completed.getinvocationlist() ) {
grade += wc();
console.writeline("worker grade= " + grade);
}
}
}
【译注:以下是上段程序输出结果
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
better…
worker grade = 4 【译注:boss打的4分也得到啦】
universe pleased with workers work
worker grade = 11【译注:peter共得了11分!】
main: worker completed work
】
异步通知:激活和放弃
————————
peter很快又发现了新的问题,他的老板和universe有时都会出现正为别的事忙碌而无暇顾及peter的工作评定,这就使得peter的工作评定时间被拖延了:
class boss {
public int workcompleted() {
system.threading.thread.sleep(3000); // 延时3秒
console.writeline("better…"); return 6; /* out of 10 */
}
}
class universe {
static int workercompletedwork() {
system.threading.thread.sleep(4000); // 延时4秒
console.writeline("universe is pleased with workers work");
return 7;
}
…
}
噢!看到了,这确实很容易造成时间的浪费,而且最让peter恼火的是,周末他与kristin的约会都被搅黄了。不行,这一定要改正!于是peter决定放弃工作评定打分而改为异步委托—事件激活:
public void dowork() {
…
console.writeline("worker: work completed");
if( completed != null ) {
foreach( workcompleted wc in completed.getinvocationlist() )
{
wc.begininvoke(null, null);
}
}
}
【译注:以下是上段程序输出结果
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
main: worker completed work //【译注:由于是异步触发事件,因此这一行先输出啦】
better… //【译注:评分已被忽略】
universe pleased with workers work //【译注:评分已被忽略】
】
异步通知:缓存池(polling)
——————————-
这就使得peter可以通知监听者的同时自己也能立即返回工作,让进程的线程池调用委托。然而不久他就发现监听者对其工作的评分丢掉了。这还了得!没有评定分数,我的绩效…peter简直欲哭无泪.他立即采用缓存池(polling)机制。peter知道他做了一件明智的事并乐意universe作为一个整体(不单单是他的boss)评判他。因此,peter异步触发事件,但定期轮询,以察看可以获得的评分:
public void dowork() {
…
console.writeline("worker: work completed");
if( completed != null ) {
foreach( workcompleted wc in completed.getinvocationlist() ) {
iasyncresult res = wc.begininvoke(null, null);
while( !res.iscompleted ) system.threading.thread.sleep(1);
int grade = wc.endinvoke(res);
console.writeline("worker grade= " + grade);
}
}
}
【译注:以下是上段程序输出结果
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
better…
worker grade = 6
universe pleased with workers work
worker grade = 7
main: worker completed work //【译注:注意这个结果到最后才输出,下一节首句意思即是如此】
】
异步通知: 委托
—————-
不幸的是,peter又倒退了—就象他一开始想避免boss站在一旁边监视他一样。也就是说,他现在要监看整个工作过程。因此,peter决定使用自己的委托作为异步委托完成时的通知方式,这样他就可以立即回去工作,而当工作被打分时,仍然可以接到通知:
public void dowork() {
…
console.writeline("worker: work completed");
if( completed != null ) {
foreach( workcompleted wc in completed.getinvocationlist() ) {
wc.begininvoke(new asynccallback(workgraded), wc);
}
}
}
private void workgraded(iasyncresult res) {
workcompleted wc = (workcompleted)res.asyncstate;
int grade = wc.endinvoke(res);
console.writeline("worker grade= " + grade);
}
【译注:以下是上段程序输出结果
worker: work started
universe notices worker starting work
worker: work progressing
worker: work completed
main: worker completed work //【译注:异步委托发生了效果,因此这一行先输出啦】
better…
worker grade = 6
universe pleased with workers work
worker grade = 7
】
圆满结局
———–
peter、他的老板和universe最终皆大欢喜,peter的老板和universe分别负责他们各自感兴趣的事件。这就减轻了执行负担和不必要的来回调用时间。peter负责在发生事件时通知与事件关联的执行者,并最终异步获得结果,而不管他们做出回应时间的长短。但是,peter也深知这一切并不像外表看起来那么简单,因为当他异步激活一个事件,与该事件关联的执行函数很可能是在另一个线程中执行,由此造成潜在的调度处理问题。不过peter与mike是好朋友,而mike则精通线程问题的处理,他会为peter不遗余力的。
现在一切ok!,关于peter的故事就讲到这里,如果大家感兴趣的话,我还有很多故事要讲的!
