现在的位置: 首页 > 专题 > 机器人 > Robotics Studio > 设计中心 > 文档 > 专题 > 机器人 > 正文

Robotics Studio学习教程:第十七天——CCR概况

2014年05月04日 Robotics Studio, 文档, 机器人 ⁄ 转载:原文链接 ⁄ 共 5700字 ⁄ 字号 暂无评论 ⁄ 阅读 1,721 次
Robotics Studio学习教程

Robotics Studio学习教程

继之前分享的一篇《Robotics Studio学习教程:第十六天——利用P3DX自动画地图》, 我们将继续分享下一篇《Robotics Studio学习教程:第十七天——CCR概况》,让我继续开始我们学习Visual Programming Language,以及使用Robotics Studio学习开发机器人应用的道路吧。

 

[Robotics Studio] CCR Overview -- Day 17

 

又到了讲解概念时间 ... 这次为大家说明的是 CCR (Concurrency and Coordination Runtime).

CCR 主要架构在 .NET CLR 上面, 而开发出 CCR 的目的, 就是为了解决当程序被切割成一个一个的组件时 (就像 DSS) , 组件之间的讯息传递, 同步性, 数据一致性, 以及协调等等问题.

先说明, 下面提到的范例程序都可以执行, 但是不够严谨(没做变量检查, 没有 Dispose), 因为是教学 (MSDN 当中很多范例也是这样).  等到你了解系统以后, 实际写 code 的时候还是多多注意小细节 (不过我自己也常常很粗心...).

而 CCR 主要分为三大部分:

 

1. Port, PortSet

CCR 透过 Port 来传递讯息 (Message Body) , 而 PortSet 就是可以传递多种不同讯息的 Port 扩充 class. 讯息在 Port 当中是以 Queue (先进先出) 的方式来储存. 一个讯息丢给某个 Port 之后, 一般都是会透过 Receiver (Arbiter.Receive) 来接收, 然后就启动该 Receiver 的 Handler.

如果你要产生一个存放 int 的 Port , 就可以写

Port<int> myPortInt = new Port<int>();

而对它传递一个讯息, 你可以写

myPortInt.Post(1);

而取出讯息, 则可以用 Test , 像是

int msg;

bool HasMsg = myPortInt.Test(out msg);

不过在多任务协调的时候, 一般都是透过 Arbiter.Receive 来处理取得讯息时该做甚么事情. 而 PortSet 就是一种可以接受多种讯息的 Port, 比方说, 要产生一个 Port 可以接收两种讯息, 分别为 int, string, 就可以这样写:

PortSet<int, string> myport = new PortSet<int, string>();

我们之前的 DSS 程序, 在 DSS Service MainPort 的部分就是 PortSet.

Port, PortSet 是 CCR 讯息传递的基本, 所以如果不了解就很难继续下去 (但是它其实是很简单的讯息储存读取器而已).

 

2. Dispatcher, DispatcherQueue, Task

就好比 Thread 执行程序代码, 在 CCR 当中, 程序的运行是以 ITask 作为执行的单位, 而 DispatcherQueue 就是以 Queue 的方式来存放许多的 Tasks (以下我提到 Task, 就是代表该 class 有支持/实作 ITask 的意思) , 最后, Dispatcher 就像是 Thread, 用来执行运作 DispatcherQueue (当中的 Tasks). 不过在极少数的状况下, DispatcherQueue 也可以被 .NET Thread Pool 来运作.

而Task 最重要的就是 Handler, 就是 Programmer 写的 delegate function. 至于这许多的 Tasks 从何而来, 如何产生, Handler (你的程序代码, delegate function) 何时要被执行 , 就是要靠接下来介绍的 Arbiter class.

 

3. Arbiter class

这是 CCR 写好的一组函式库, 负责用来撰写处理讯息所需要用到的协同处理函式. 通常使用者 (programmer) 就是透过撰写 delegate function, 然后指定当某种情境发生时 (比方说, Receive Task 就是某个 Port 收到讯息时), CCR 就会执行该 delegate function (透过 ITask).

以下我开始介绍 Arbiter 的所有 Member (都是 static function, 所以都是直接使用), 这可是 CCR 的重头戏 (不过我只介绍基本使用方法, 进阶的使用方法就太多了):

Arbiter.Recive

产生一个 Receive Task , 就是当某一个 port 收到讯息时, 就执行 Handler (某段程序代码).

比方说, 以下的程序产生一个 Receive Task, 就是当一个 Port<int> 收到 int 讯息时就把它从 Console 输出. (但是该 Task 还不会被执行, 因为我们只是产生了 Recive Task)

var myport = new Port<int>();

var mytask = Arbiter.Receive(true, myport, i => Console.WriteLine(i));

其中, 第一个参数如果是 true, 表示这个 Task 一直都要存在, 不会消失, 如果是 false, 则表示仅仅执行一次, 这个 Task 就消失.

Arbiter.Activate

使用某个 DispatcherQueue 来执行一系列的 Tasks.

比方说我们要产生一个 DispatcherQueue, 然后利用该 DispatcherQueue 来执行一个 Receive Task, 就可以写成下面这样的 code :

var taskQueue = new DispatcherQueue("myqueue", new Dispatcher());

var myport = new Port<int>();

Arbiter.Activate(taskQueue, Arbiter.Receive(true, myport, msg => Console.WriteLine(msg)));

如果接下来有程序执行 myport.Post(3) , 就会启动执行 Receiver Task, 也就是在 Console 印出 3 .

Arbiter.Choice

有点像是 switch case , 是属于 Task 的分支流程处理用的, 它会产生一个 Choice Task , 而只会处理众多 Handlers 的其中之一 (或众多 Tasks 的其中之一个 Handler).

比方说有个 PortSet 叫做 myport,  可以接受 int, string 这两种讯息, 于是你可以用

Arbiter.Choice(myport, intmsg => Console.WriteLine(intmsg), stringmsg => Console.WriteLine(stringmsg));

来表示当 myport 收到 int 时, 或是收到 string 时要作的事 (但只有二选一), 你也可以写成这样:

Arbiter.Choice(

Arbiter.Receive<int>(false, myport, intmsg => Cosnole.WriteLine(intmsg)),

Arbiter.Receive<string>(false, myport, strmsg => Console.WriteLine(strmsg)));

因为 Arbiter.Choice 一旦处理完分支 Task 就把所有 Task 销毁, 所以你不能传永久存在的 Receive Task 给它欧.

Arbiter.FromHandler , Arbiter.FromIteratorHandler

将 delegate function 转成 Task, 这样可以提供排程 (DispatcherQueue) 使用.

很简单, 想把某段程序代码变成 Task, 就这样作:

var task = Arbiter.FromHandler( () => { Console.WriteLine("Hello World"); });

你也可以写一个函式是回传 IEnumerator<ITask> , 然后就是利用 FromIteratorHandler 来把该函式转成 Task.

Arbiter.Interleave

以前, 你会用 lock, 或 ReaderWriterLock 来处理 multi-thread 的程序处理数据的问题, Arbiter.Interleave 就是用来解决这个问题.

你现在知道 ServiceBehavior.Concurrent, ServiceBehavior.Exclusive, ServiceBehavior.TearDown 是如何实现了, 基本上之前我们的 Start() 都会呼叫 base.Start(), base.Start() 当中就是利用 Arbiter.Interleave (产生一个 MainInterleave) 来把所有的 ServiceHandler 集合起来执行.

所以这个函式会产生一个 Task 集合 , 你要传给它三种 Task 集合,  TearDownReceiverGroup 是属于结束的时候呼叫的, Arbiter.Interleave 保证它不会跟其他任何 Task 同时执行, 但是它必须是一次性的 Receiver (one time only).

ExclusiveReceiverGroup 是属于写入数据时呼叫, Arbiter.Interleave 保证它不会跟其他任何 Task 同时执行.

ConcurrentReceiverGroup 是属于读取数据时呼叫, Arbiter.Interleave 让这些可以同时被执行但不会跟 ExclusiveReceiverGroup, TearDownReceiverGroup 同时执行.

MSDN 当中的范例就像是这样:

  1.   // activate an Interleave Arbiter to coordinate how the handlers of the service
  2.   // execute in relation to each other and to their own parallel activations
  3.   Arbiter.Activate(_taskQueue,
  4.       Arbiter.Interleave(
  5.       new TeardownReceiverGroup(
  6.       // one time, atomic teardown
  7.           Arbiter.Receive<Stop>(false, _mainPort, StopHandler)
  8.       ),
  9.       new ExclusiveReceiverGroup(
  10.       // Persisted Update handler, only runs if no other handler running
  11.           Arbiter.Receive<UpdateState>(true, _mainPort, UpdateHandler)
  12.       ),
  13.       new ConcurrentReceiverGroup(
  14.       // Persisted Get handler, runs in parallel with all other activations of itself
  15.       // but never runs in parallel with Update or Stop
  16.           Arbiter.Receive<GetState>(true, _mainPort, GetStateHandler)
  17.       ))
  18.   );

 

而 CCR 实现这个 Arbiter.Interleave 不是靠传统的 lock, 或是 ReaderWriterLock, 而是靠内部的 TaskQueue 来实作的, 它也不需要知道哪些数据是你所要保护的, 它只是负责避免哪些程序不可同时执行, 以及允许哪些程序可以同时执行.

Arbiter.JoinedReceive

假如你想要等候两个 Port 都分别收到讯息时才要作处理 (执行 Handler) 的话, 这个就是你要的.

比方说下面的 code:

Arbiter.JoinedReceive<int, string>(false, myport, myport2, (intmsg,strmsg)=>Console.WriteLine(intmsg+","+strmsg))

就是表示产生一个 JoinedReceive Task, 当 myport 收到 int 讯息, myport2 收到 string 讯息, (不管先后顺序, 会一直等到两个都收到讯息), 就在 Console 作输出.

Arbiter.MultipleItemReceive

跟 Arbiter.JoinedReceive 不太一样的地方在于, 它是看讯息的数量, 当一定数量的讯息送到一个或多个 Port 的时候, 就会启动 Handler.

比方说下面这个 code 就是当累积五个 int 讯息送到 portInt 的时候, 就会启动 Handler, 把这五个 int 输出到屏幕上.

Arbiter.MultipleItemReceive(true, portInt, 5,
  intArray => Console.WriteLine(string.Join(",", new List<int>(intArray).ConvertAll<string>(i => i.ToString()).ToArray())));

一旦你了解了 Arbiter 的函式, 还有 DSSP 的类型, 相信你再去看 documentation 当中的 MsrsUserGuideExpress.chm 就会获益良多啰.

题外话: 前天我才去天珑书局翻了 Professional Microsoft Robotics Developer Studio (Wrox Programmer to Programmer) ,  发现里面程序主要是 RDS 1.5 的, 难怪跟 RDS 2008 不太一样...  虽然基本功都是相通的,  但是其实还是有点小变化, 所以还是读 documentation 好了...

 

让我们继续一下章教程:

《Robotics Studio学习教程:第十八天——通过 Web Interface 控制DSS》

fgx

分享到:

Wopus问答

×