NCindy

.net平台上的高性能网络程序开发框架

Ports和PortSets

原文链接

CCR port是最常见的原语,它被用作两个组件的交互点。Port实现了两个接口:IPortReceive和IPort。这些接口从逻辑上分离了向Port中增加元素的方法和从Port中检索元素的方法,或者附加删除元素和异步执行的代码。这些接口不是强类型的,但是默认实现Port<>有一个泛型参数。

注意:类型标识符后面的<>标记表明这个类型是一个泛型类型。例如Port<int>是一个带了一个Int32类型参数的Port实现。

投递元素(Posting Items)

向一个port中添加元素是一个异步操作。当没有仲裁器(arbiters)被附加到port时,元素被添加到队列。如果有仲裁器,port会逐个调用仲裁器来对被添加到元素进行计算,然后决定是否需要创建一个Task和进行执行调度。这个计算是快速的非阻塞的操作,所以Post方法会尽可能快的把控制权交回给调用者。这是CCR异步操作的源头:程序快速的提交元素到ports,然后当某些情况出现时引发Tasks被调度到某个另外的线程。

例1.

// Create port that accepts instances of System.Int32 Port<int> portInt = new Port<int>();
// Add the number 10 to the port portInt.Post(10);
// Display number of items to the console Console.WriteLine(portInt.ItemCount);   

在例1中,我们创建了一个只接受整型的port。然后我们向port中添加了一个元素(数字10),然后我们检查port的ItemCount属性,这时它应该是1。

 

检索元素(Retrieving Items)

从一个port中检索元素时有两种情况:

  1. port被用作“被动”队列:当元素被投递后没有任务会被调度。在这种情况下可以调用Test方法来检索元素。Test方法永远不会阻塞。如果当前没有元素在port中,Test方法会返回false然后将out 参数设置为null。这种情况在你有某种其他的机制来根据某个事件执行代码,而且你只是想把port当作一个高效的FIFO队列的时候非常有用。它提供了迟后附加一个接收者到port来执行port中任何元素的灵活性。
  2. port被用作“主动”队列:由于一个或者多个仲裁器被注册到port,所以当元素被投递后会有任务被调度执行。这是使用CCR port最常见的情况。它是主要的并发源,因为元素投递会导致用户代码被调度而且任意代码都可以潜在的并行执行。

例2.

// Create port that accepts instances of System.Int32 Port<int> portInt = new Port<int>();
// Add the number 10 to the port portInt.Post(10);
// Display number of items to the console Console.WriteLine(portInt.ItemCount);
// retrieve the item using Test int item; bool hasItem = portInt.Test(out item);
if (hasItem) {      Console.WriteLine("Found item in port:" + item); }

上面的这个例子中,我们使用Test方法来检索被投递的元素。这个元素会在一个线程安全的方法中被删除,然后ItemCount属性应该变为0。

例3.
// Create port that accepts instances of System.Int32 var portInt = new Port<int>();
// Add the number 10 to the port portInt.Post(10);
// Display number of items to the console Console.WriteLine(portInt.ItemCount);
// create dispatcher and dispatcher queue for scheduling tasks Dispatcher dispatcher = new Dispatcher(); DispatcherQueue taskQueue = new DispatcherQueue("sample queue", dispatcher);
// retrieve the item by attaching a one time receiver Arbiter.Activate(     taskQueue,     portInt.Receive(delegate (int item) // anonymous method     {         // this code executes in parallel with the method that         // activated it                                 Console.WriteLine("Received item:" + item);     } )); // any code below runs in parallel with delegate

在例3中我们使用和例1同样的步骤,但是使用Arbiter.Active方法代替Test方法来注册一个简单的接收者仲裁器到port。接收者关联到一个用户提供的delegate,在这个例子中delegate是一个内联的匿名方法。delegate将会在一个关联在DispatcherQueue实例上的dispatcher线程中执行。Dispatcher和DispatcherQueue类不是这里的赘述,更多关于他们的细节将会在《任务调度》中描述。需要注意的是:delegate总是和例子中的主体以并行方式运行。在本例中,当delegate运行之后,接收者自动从port中删除。后面会有更多关于接收者和仲裁器的介绍。

 

查看Port的状态

下面的这些Port<>类型的方法和属性对于在运行时查看Port的状态很有用,无论是在调试中或者常规代码逻辑中。

  • ToString - 这个方法重写(override)了默认的ToString实现,它将以易读的形式输出port中元素的数量和port所关联的接收器的层次。
  • ItemCount - 当前队列中的元素数。注意,如果一个或者多个持久化(也就是说在处理了一个元素后依旧附加在port上)的仲裁器被注册在port上,无论何时ItemCount都会是0。大多数仲裁器会使队列中的元素立即转化为被调度执行的任务,所以被投递到port中的元素实际上永远不会出现在port的元素队列中。

 

Port Sets

由于Port<>类型只有一个泛型参数,所以经常把多个独立的Port<>实例关联在一起放到一个单一的容器中作为一个实体。PortSet<>泛型类允许这样的归类,并且它是定义一个CCR软件组件接口的最常见的方式。CCR组件只需要暴露出包含N个相互独立的Port的强类型的PortSet而不需要暴露出一组公共的方法。

构造一个PortSet实例

有两种办法可以创建一个新的PortSet实例:

  1. 在编译时使用泛型参数来定义,PortSet将支持的Port<>的数量和类型。CCR在桌面CLR上提供了有多达20个泛型参数的PortSet实现。在.net Compact framework中因为JIT的限制,提供了最多8个泛型参数的PortSet。使用这种方法给你最好的类型安全和最好的运行时性能,但是它限制了portset能支持的类型数量。
  2. 使用接受类型参数列表的初始化构造函数。你可以指定任意数量的类型,Port<>实例会在运行时创建。某些方法,例如针对每个消息类型的Post方法就无法被使用(必须使用PostUnknownType或者TryPostUnknownType)并且会造成一点性能影响,因为需要在运行时根据有效类型表对元素进行类型的检查和比较。

 

例4.

// Create a PortSet using generic type arguments
var genericPortSet = new PortSet<intstringdouble>(); genericPortSet.Post(10); genericPortSet.Post("hello"); genericPortSet.Post(3.14159);
// Create a runtime PortSet, using the initialization  // constructor to supply an array of types PortSet runtimePortSet = new PortSet(     typeof(int),     typeof(string),     typeof(double)     );
runtimePortSet.PostUnknownType(
10); runtimePortSet.PostUnknownType("hello"); runtimePortSet.PostUnknownType(3.14159);

在例4中,我们展示了如何创建一个含有三个不同类型的PortSet对象。第一种方式是使用泛型类型参数,第二种方式是在运行时使用类型数组。非泛型PortSet可以通过为其继承类增加强类型的Post方法来提供和泛型PortSet一样的编译时安全。

例5.

/// <summary>
/// PortSet that accepts items of int, string, double
/// </summary>
public class CcrConsolePort : PortSet<intstringdouble>
{
}
/// <summary>
/// Simple example of a CCR component that uses a PortSet to abstract
/// its API for message passing
/// </summary>
public class CcrConsoleService
{
    CcrConsolePort _mainPort;
    DispatcherQueue _taskQueue;

    
/// <summary>
    
/// Creates an instance of the service class, returning only a PortSet
    
/// instance for communication with the service
    
/// </summary>
    
/// <param name="taskQueue"></param>
    
/// <returns></returns>
    public static CcrConsolePort Create(DispatcherQueue taskQueue)
    {
        var console 
= new CcrConsoleService(taskQueue);
        console.Initialize();
        
return console._mainPort;
    }

    
/// <summary>
    
/// Initialization constructor
    
/// </summary>
    
/// <param name="taskQueue">DispatcherQueue instance used for scheduling</param>
    private CcrConsoleService(DispatcherQueue taskQueue)
    {
        
// create PortSet instance used by external callers to post items
        _mainPort = new CcrConsolePort();
        
// cache dispatcher queue used to schedule tasks
        _taskQueue = taskQueue;
    }

    
private void Initialize()
    {
        
// Activate three persisted receivers (single item arbiters)
        
// that will run concurrently to each other,
        
// one for each item/message type
        Arbiter.Activate(_taskQueue,
            Arbiter.Receive
<int>(true, _mainPort, IntWriteLineHandler),
            Arbiter.Receive
<string>(true, _mainPort, StringWriteLineHandler),
            Arbiter.Receive
<double>(true, _mainPort, DoubleWriteLineHandler)
        );
    }

    
void IntWriteLineHandler(int item)
    {
        Console.WriteLine(
"Received integer:" + item);
    }
    
void StringWriteLineHandler(string item)
    {
        Console.WriteLine(
"Received string:" + item);
    }
    
void DoubleWriteLineHandler(double item)
    {
        Console.WriteLine(
"Received double:" + item);
    }
}

 

在上面的例子中,我们定义了一个简单的类,CcrConsolePort,它从一个有三个类型参数的PortSet继承。这使得后面对PortSet的使用更加具有可读性,因为我们不需要重复泛型定义。CcrConsoleService类实现了一个CCR的通用模式,它又一个静态方法来创建实例对象和返回与之通信的私有PortSet实例。每个处理器的激活都是并发的(假设关联在dispatcher queue上的dispatcher的多个线程都是可用的)

 

类型安全(Type Safety)

PortSet类允许枚举所有的port实例和它支持的元素类型。泛型PortSet也提供了方法来自动调用正确的Port<>实例和可以在类型参数是对PortSet<>有效的时候自动将PortSet<>实例隐式转换成Port<>实例的运算符。这个特性在向PortSet中的一个Port上注册接收器的时候很有用,只需要类型参数就可以了。

例6.

var portSet = new PortSet<intstringdouble>();

// the following statement compiles because of the implicit assignment operators
// that "extract" the instance of Port<int> from the PortSet
Port<int> portInt = portSet;

// the implicit assignment operator is used below to "extract" the Port<int>
// instance so the int receiver can be registered
Arbiter.Activate(_taskQueue,
    Arbiter.Receive
<int>(true, portSet, item => Console.WriteLine(item))
);

在例6中演示了在两种情况下使用隐式转换运算符。

1.将一个PortSet中的适当的Port<>实例赋值给另外一个Port<>变量

2.抽取适当的Port<>实例,于是它可以被用于注册一个仲裁器(Arbiter)。

posted on 2007-12-10 14:19  iceboundrock  阅读(935)  评论(0编辑  收藏  举报

导航