技术开发 频道

C# Actor消息执行:解决方案

    看似美好的实现

    等一下,接口只是一种“协议”,但是“消息”还必须是一个实体,一个对象,并且“携带”了这个协议才能在Actor之间传递啊。这个对象除了携带协议所需要的数据以外,还要能够告诉接受方究竟该“操作什么”。“操作”带上“数据”,于是我就想到了“委托”。例如,如果我们想要发送一个“协议”,叫做IDoHandler,那么我们便可以构造一个Action<IDoHandler>对象——这

    正是Lambda表达式的用武之地:

Action<IDoHandler> m = x => x.Do(0, 1, 2, ...);
    好,那么我们还是用乒乓测试来尝试一番。我们知道,乒乓测试会让Ping对象和Pong对象相互发送消息,我们各使用一个“消息组”,也就是“接口”来定义消息:

public interface IPongMessageHandler { }

public interface IPingMessageHandler { }

      那么,Ping和Pong两个Actor类型又该如何定义呢?我们知道,Ping需要处理Pong发来的消息,因此它需要实现IPongMessageHandler接口,并且需要接受类型为Action<IPongMessageHandler>的消息。Pong与Ping类似,因此它们的定义为: 

public class Ping : Actor<Action<IPongMessageHandler>>, IPongMessageHandler
{
private int m_count;

public Ping(int count)
{
this.m_count = count;
}

protected override void Receive(Action<IPongMessageHandler> message)
{
message(this);
}

...
}

public class Pong : Actor<Action<IPingMessageHandler>>, IPingMessageHandler
{
protected override void Receive(Action<IPingMessageHandler> message)
{
message(this);
}

...
}
 

    
    从代码上看,实际操作中我们并不需要让Ping或Pong直接继承Handler接口,只要最终提供一个对象给message执行即可。严格说来,“接口”只是一个“消息组”,具体的“消息”还是要落实到接口中的方法。定义了Ping和Pong之后,我们便可以明确接口中的方法了(确切地说,是明确了方法的参数):

public interface IPongMessageHandler
{
void Pong(Pong pong);
}

public interface IPingMessageHandler
{
void Ping(Ping ping);
void Finish();
}
 

    
    使用了接口,自然就要提供方法的实现了。我们先从典型而简单的Pong对象看起:

public class Pong : Actor<Action<IPingMessageHandler>>, IPingMessageHandler
{
...

#region IPingMessageHandler Members

void IPingMessageHandler.Ping(Ping ping)
{
Console.WriteLine("Pong received ping");
ping.Post(h => h.Pong(this));
}

void IPingMessageHandler.Finish()
{
Console.WriteLine("Finished");
this.Exit();
}

#endregion
}
 

    
    原本需要在得到消息之后,根据消息的内容作出不同的响应。而现在,消息会被自动转发为接口中的方法调用,我们只需要实现特定的方法即可。在Ping方法中,我们会得到一个Ping类型的对象——于是我们再向它回复一个消息。消息的类型是Action<IPongMessageHandler>,可以看出,使用Lambda表达式构造这样一个消息特别方便。

  Ping类也只需要实现IPongMessageHandler即可,只是这段逻辑“略显复杂”:

public class Ping : Actor<Action<IPongMessageHandler>>, IPongMessageHandler
{
...

public void Start(Pong pong)
{
pong.Post(h => h.Ping(this));
}

#region IPongMessageHandler Members

void IPongMessageHandler.Pong(Pong pong)
{
Console.WriteLine("Ping received pong");

if (--this.m_count > 0)
{
pong.Post(h => h.Ping(this));
}
else
{
pong.Post(h => h.Finish());
this.Exit();
}
}

#endregion
}
 

    收到Pong消息之后,将count减1,如果还大于0,则回复一个Ping消息,否则就回复一个Finish并退出。最后启动乒乓测试:

new Ping(5).Start(new Pong());
    由于使用了接口作为消息的协议,因此无论是编辑器还是编译器都可以给我们足够的支持。同时,对于消息的处理也无须如上一篇文章那样不断进行判断和类型转换,代码可谓流畅不少。

    致命的缺陷
  
    虽说没有完美的东西,但目前的缺陷却是致命的。

    在实际使用过程中,消息的“发送方”和消息的“接收方”应该完全无关,它们互不知道对方具体是谁,只应该基于“协议”,也就是“接口”来实现。可惜在上面这段代码中,很多东西都被“强横”地限制住了。例如,Ping消息会附带一个ping对象作为参数,ping对象会等待一个Pong消息。但是,发送Ping消息(并等待Pong消息)的一方很可能是各种类型的Actor,不一定是Ping类型。有朋友可能会说,那么我们把IPingMessageHandler的Ping方法的签名改成这样,不就可以了吗?

void Ping(Actor<Action<IPongMessageHandler>> ping)
   
    是的,此时的ping,的确是在“等待Pong消息的Actor对象”。但是,这意味着ping对象它也只能是这个指明的Actor类型了。在实际使用过程中,这几乎是不可能的事情。因为一个Actor很可能会接受各种消息,它很难做到“一心一意”。因此这篇文章所提出的做法,几乎只能满足如乒乓测试这样简单的Actor模型使用场景。我们必须改变。

    改变的方式有不少,从“向弱类型妥协”到“利用.NET 4.0中的协变/逆变”,都可以满足不同的场景——不过我们还是下次再说吧。

0
相关文章