task 的目的
做过界面的编程的同学可能会有这种经历,界面不响应,那,其实程序总是在后台运行,但是后台可能是个for循环,那么界面的点击等事件都不能执行。
在windows界面编程中利用了事件机制来做,维护了事件的队列,然后有一个线程不停的取出事件然后回调注册的事件处理函数,每个事件处理函数必然是短小的,否则会独占处理器太久,其他操作得不到响应。
在Qt中,为了维持对用户的响应,使用的是signal函数,signal函数触发slot的操作,理论上也应该是短小的操作。
在两种机制中,主线程负责ui方面的响应,要不就是把发生的时间记录下来,要不就是直接signal信号出去,然后立即返回。总体的规则是负责ui的线程中不能出现长时间占用cpu的操作。
task的出现和以上情况是一致的,在使用post task
后,函数立即返回,可以执行其他操作,其他组件也可以继续相应用户,所有的task应该是短小的操作,以保证其他的task不会产生太大的延迟。
TinyOS中采用了队列的机制来循环处理task,用线程来讲就好像,由boot触发的main函数像一个主线程,用来记录用户的操作以及各种task,而schedule组件就像是一个负责处理的线程,循环的处理用户已经post的线程,这样,用户的操作,如亮灯、终端等,可以得到及时的响应,外界的信息也不会那么容易的丢失。
task 的机制
在tinyos 1.x中task采用的方式是post task
,在2.x中使用BasicTasic接口来进行task的注册,但完全可以用1.x中的方法。
前面提到过post task
只是往task队列中添加一条task调用记录,然后由scheduler循环处理,在post task
和scheduler真正调用task时,一定会产生延迟,也就是说task的调用是推后的函数调用。
因为在1.x中schedular貌似没有设置task队列的长度,那么意味着一个组件可以无限post task
,但实际上这种情况会使得一个组件独占cpu资源。
为了避免这种情况,在2.x中schedular设置了的task的调用数组,数组中的每一项是注册过的task,当task被调用了,就相应的置位,关于顺序问题应该是有做记录(我不知道,求告诉QAQ),这样可以防止一个组件post过多相同的task独占资源。
task 和函数的不同
执行操作不同
最主要的不同在于调用,这也是task存在的意义,就像前面说的,task是为了相应用户而生,调用 task 时在 post 后立即返回,而函数要执行完后返回。
长相也不同
task的标准形式如下
task void 函数名()
{
// do task
}
这意味着task没有返回值,也没有参数。task的思想是为了执行一段操作,调用task时立即返回,所以没有返回值是情理之中,但post task
却有返回值,我们等会说。
task 的参数我的想法是,TinyOS 的思想是节点的内存有限,所以尽可能减少临时变量和动态变量这一类变量,所以可以看到文档建议一些变量最好直接在组件中进行定义,没有那么多的内存空间来做函数的栈存储临时变量。
基于以上分析,task不带参数,也不帮你存储参数,但由于这个性质,task运行时可能会产生问题,我们在后面会说到。
是否允许中断,应该说是抢占?
可以明确的是,task是允许中断(我觉的更确切应该说是抢占)的,我想这也是保持用户响应的一部分。同时,我的想法是普通函数是不允许中断的,因为task是允许中断的。。。
async 函数意味着可以异步执行,TinyOS规定 async 函数中不能调用普通函数,也就是同步函数,异步的意思貌似是可以抢占其他函数的执行,或者说是打断,同时自己也可以被抢占。那么如果想要在 async 函数中执行同步代码,就要用到task,也是唯一方式。
task 返回失败的情况
首先post task
是有返回值的。
为了防止同一组件不停的post task
,Tinyos 2.x中post在有同名task正在队列并且没有执行时返回Fail,应该就是检查task调用数组了,这以为着你开启一个for循环来post数组是不允许的,如下
for (int i = 0; i < 10; i++)
{
post process_message();
}
上面这代码,如果第一个post成功,并且process_message
函数执行有一定时间,那么根据TinyOS规定,一个process_message
在执行,一个process_message
函数在队列,其他的处理函数就都没有得到执行,返回的是Fail。在这种情况下,你的想法是处理10条消息,但实际上只处理了2条。
这样看来,这种情况下你所post task
的数目与实际执行的task的数目并不一定相同。
但TinyOS允许task自身调用,如下
task void process_message()
{
// start processing message
...
// finish processing message
process_message()
}
通过这种方式,task应该是不会返回Fail的,因为task实在结尾处调用自身,如果设置一个counter的话,应该可以实现上面的for循环调用。
In TinyOS 2.x, a basic post will only fail if and only if the task has already been posted and has not started execution.
task 只关注当前
按照线程的思维方式,线程所使用的变量都是进程的,这意味着如果其他线程修改了进行的变量,当前线程可能并不知情。
task执行任务是推迟的,并且task是允许中断的,这意味这在使用post task
后,组件的值随时可能发生变化,任何一个中断都有可能修改数据,你post task
时所认为task使用的变量,当scheduler真正调用时,可能已经物是人非。
task执行时永远使用当前组件内最新(updated)的变量值,这意味着你在post task
时最好想清楚当task执行时所采用的组件变量是否已经发生改变,必要的话需要采取一定保护变量的措施,如设置变量标志位等,如使用send时采用send_busy标志应该是常用的方法。
其他
我看到 TaskBasic 接口里好像有优先级的设定,这意味着如果加入优先级, task 的顺序可能会发生不可控的变化,使用 task 时需要更为细致。
最后
作者水平有限,准确信息请参考TinyOS官方文档