[连载] 用C#进行思考(二)Pull模式和Push模式

本文探讨了生产者和消费者模式中的Pull与Push两种交互方式,分析了它们的特点、适用场景及优缺点,并通过实例展示了如何在编码实践中选择合适的模式。

  上文提到了生产者和消费者角色。生产者和消费者是针对类型而言的。一旦确定了生产者和消费者角色,后续编码过程中使用的调用方式也就严格确定了,如果消费者需要调用生产者,就直接使用方法调用;如果生产者需要调用消费者的某些功能,则需要使用委托或事件进行回调。

  由此而看,当生产者和消费者互相作用,以完成一项操作时,存在两种方式——调用或回调。当采用方法调用完成一个操作时,我们成这种模式为“Pull模式”;而当采用回调完成一个操作时,我们成这种模式为“Push模式”。

  前文提到了WinForm的例子,在窗体中使用控件时,控件类型是生产者,而窗体类则是消费者。以Button类和MyForm(继承自Form类)为例。

class MyForm:Form
... {
Buttonm_btn;

publicForm()
...{
m_btn
=newButton;

m_btn.Text
="OK";//<----使用pull模式
m_btn.Click+=newEventHandler(m_onOKClick);//<----为使用push模式作出准备
}


voidm_onOKClick(objectsender,EventArgse)//<----当用户单击按钮时,m_btn使用push模式回调该方法
...{
MessageBox.Show(
"Hello");
}

}

  在这个例子中,为按钮的Text属性赋值(实际上是调用其set访问器),使用的就是pull模式,而按钮通过Click事件(其实是一个特殊的委托)调用MyForm的m_onOKClick方法,便使用了push模式。

  Pull模式的特点是,消费者占主动地位,主动发起对生产者的调用;Push模式的特点是生产者占主动地位,发起对消费者的调用。在使用Pull模式时,方法调用的时机是可预测的,也就是,我们很清楚地知道,当代码执行到m_btn.Text = "OK"时,Button.Text属性的set访问器被调用。而在使用Push模式时,方法调用的时机是不可预测的,我们并不知道m_onOKClick方法什么时候被调用。

  但值得注意的是,Push模式虽然看起来是生产者主动,其实是有一个重要前提的:也就是在操作开始之前,消费者必须主动地向生产者“注册”一下。(如为事件添加事件处理器,或者调用生产者的某一个方法,向其中传递一个委托对象。)这就相当于消费者向生产者授权,说“一会你可以主动调用我,我允许你这样做”。少了这个注册(授权)的过程,生产者还是不能调用消费者。

  Pull模式的优点在于,它符合人类的思维方式,obj.Operate()可以轻易地用“谁谁谁,去做什么什么事情”这样的口语来表达。(正因为如此,很多初学者开始写代码都是一大堆方法互相进行调用。)Pull模式的缺点是,其调用时机太明确了,失去了灵活性;另外,如果调用没有完成,当前线程会一直阻塞在这里。

  Push模式的优点在于,我们并须需要知道一个方法什么时候被调用,只要知道一旦发生调用,我们应该做什么,就好了。另外,在需要发生轮询的地方,如果使用Push模式,可以避免消费者一端通过无穷循环来检测生产者的状态。Push模式的缺点在于,我们很难知道调用的确切时机,所以很多人不适应(人类天生对不可预测的东西有恐惧感)。另外,使用了Push模式,就很难通过单步跟踪等手段进入到一个方法内部,给调试工作带来了困难,只能通过断点的方式对局部代码进行监视。

  最后还要提到的有两点:

  一。具有使用与被使用关系的两个类型之间(生产者与消费者之间)会发生很多操作,这些操作既可以使用Pull模式,也可以使用Push模式。也就是说,确定好了生产者和消费者之后,Pull/Push模式并不确定。因此,在设计过程中,确定好生产者消费者以后,还要研究两者之间会发生哪些操作,并根据需求和实际场景决定每个操作使用哪种方式。

  二。同一个操作往往既可以用Pull模式完成,也可以用Push模式完成。例如在使用OpenFileDialog选择一个文件时,可以这样:

void m_onOpen( object sender,EventArgse)
... {
OpenFileDialogofd
=newOpenFileDialog();
//为ofd设置属性,略

DialogResultr
=ofd.ShowDialog();//<----一个操作,使用pull模式

if(r!=DialogResult.OK)//因为下面一个操作使用了pull模式,所以这个判断是必须的
return;

stringfilename=ofd.FileName;//<----又一个操作,使用pull模式
//对文件进行相关操作,略
}

  也可以:

void m_onOpen( object sender,EventArgse)
... {
OpenFileDialogofd
=newOpenFileDialog();
//为ofd设置属性,略

ofd.FileOk
+=newCancelEventHandler(m_openFileOK);//<----为第二个操作使用push模式做好准备

ofd.ShowDialog();
//上一个例子中的第一个操作,还是使用pull模式,但无需记录返回值,也不用进行判断
}


void m_openFileOK( object sender,CancelEventArgse) // <----使用push模式完成上例中第二个操作
... {
OpenFileDialogofd
=(OpenFileDialog)sender;

stringfilename=ofd.FileName;
//对文件进行相关操作,略
}

  这两种方式各有优劣,我想各位看官都能看出,不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值