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官方文檔
文章列表