前文提到过回调。也就是当发生不得不由生产者调用消费者时,不能直接使用方法调用,必须使用回调。
(插:虽然在这里我对回调提出了新的定义,但实际上与传统意义上的回调并不冲突。试想一下Windows中的回调,基本上都是系统为我们提供了一写功能[生产者],而我们在调用这些功能时,又必须为其提供一个操作,以便其进行调用,这时才会产生回调。)
在.NET中,使用委托实现回调。委托有两种方式——将委托作为参数传递、将委托作为类的域(Field)成员。前者就是这里要说的,很简单;而后者,放在下一篇文章再说,作为域成员的委托直接导致了事件的诞生。
考虑在老板(消费者)和员工(生产者)之间,老板希望在员工做完工作后进行检查。
public class Boss
... {
Employee m_anEmployee = new Employee();
public viod DispatchJob()
...{
try // 异常处理在大多数情况下是需要的,因为工作不一定成功
...{
m_anEmployee.DoJob();
m_check(); // 检查工作
// 将“工作状态”改为“完成”
}
catch(Exception ex)
...{
// 处理异常情况
// 将“工作状态”改为“错误”
}
}
private void m_check()
...{
// 执行对工作的检查
}
}
// Employee.cs
public class Employee
... {
public void DoJob()
...{
// 完成工作,出错时抛异常
}
}
在这里,作为消费者的老板,做了太多的事情(无论出于实际生活,还是出于在编程中的体验,都是不合理的)。因此这里也暴露出一种经验,就是当你发现你的一个类型,作为生产者的角色时,其实现太过简单,那么说明你的设计一定是有问题的。
考虑这样一个问题,如果在使用Employee时,忘记作异常处理怎么办?忘记了调用check,没有检查工作怎么办?又或者不忘,那么每写一个用到了员工的类型或方法时,都要添加这样的代码,重复工作也是不能忍的。尤其是当DoJob方法要消耗大量时间时,每个调用都要在Boss中单独开启后台线程,工作量繁重且易出错。注意在这种方式下不能在DoJob中开启后台线程,否则就不能保证m_check方法是在DoJob完成后调用的。
考虑使用回调。
public class Boss
... {
Employee m_anEmployee = new Employee();
public viod DispatchJob()
...{
JobFinishedCallback cb = new JobFinishedCallback(m_check);
m_anEmployee.DoJob(cb); // 使用回调,简化了调用
}
private void m_check(JobState s)
...{
if(s = JobState.Finished)
// 进行具体的检查
else
// 处理错误
}
}
// Employee.cs
public delegate void JobFinishedCallback(JobState s);
public enum JobState
... {
Finised, Error
}
public class Employee
... {
public void DoJob(JobFinishedCallback cb)
...{
try // 可以在DoJob内部处理错误
...{
// 完成工作,出错时抛异常
if(cb != null)
cb(JobState.Finished);
}
catch(Exception)
...{
if(cb != null)
cb(JobState.Error);
}
}
}
现在,Boss的实现变得非常简单。如果需要添加后台线程,也只需在Employee中添加即可。甚至,还可以将多个方法链接到cb回调中(别忘了C#使用多播委托),用一系列方法来处理JobFinished这件事。(咦,事件的概念是不是冒出来了?且听下回分解。)