上文提到了生产者和消费者角色。生产者和消费者是针对类型而言的。一旦确定了生产者和消费者角色,后续编码过程中使用的调用方式也就严格确定了,如果消费者需要调用生产者,就直接使用方法调用;如果生产者需要调用消费者的某些功能,则需要使用委托或事件进行回调。
由此而看,当生产者和消费者互相作用,以完成一项操作时,存在两种方式——调用或回调。当采用方法调用完成一个操作时,我们成这种模式为“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;
//对文件进行相关操作,略
}
这两种方式各有优劣,我想各位看官都能看出,不再赘述。
本文探讨了生产者和消费者模式中的Pull与Push两种交互方式,分析了它们的特点、适用场景及优缺点,并通过实例展示了如何在编码实践中选择合适的模式。

被折叠的 条评论
为什么被折叠?



