.Net Micro Framework中的線程
March 19, 2009
黃季冬
http://fox23.cnblogs.com/
摘要
新一代嵌入式微架構.Net Micro Framework提供了對線程調度的支援,和它的兩位前輩(.Net Framework, .Net Compact Framework)相比,Micro Framework並不需要依賴於OS提供的線程管理的服務,因為Micro Framework本身就是一個“類作業系統”。本文介紹了Micro Framework中的多線程原理,以及.Net Micro Framework中WPF的多線程編程。
簡介
作為.NET家族的一名新成員,.Net Micro Framework是微軟專門針對超輕量級平台(主要是一些低端的32位微處理器)設計的軟體架構。其結構1:
圖1. .Net Micro Framework 架構
.Net Micro Framework有且僅有一條本地執行線程,這條線程上跑的就是.Net Micro Framework CLR(TinyCLR)。TinyCLR是一個可以自己引導的運行環境,和完整版本的.Net一樣會管理它自己涉及的記憶體。所以可以認為.Net Micro Framework不需要依賴作業系統提供線程和記憶體管理的服務。所以,完全可以把.Net Micro Framework移植到沒有OS,甚至沒有記憶體管理單元(MMU)的某些ARM7處理器上。
儘管只在單一的線程上執行,但是CLR要求對驅動的調用“看起來”是非同步,也就是說這些調用會立即返回,而不是一直阻塞直到該任務的硬體I/O完成。這和Windows下的APC(asynchronous procedure call)的實現非常類似。TinyCLR的線程調度依賴於APC的完成模式,APC的實現依賴於圖1中PAL層的定時器(Timer)的實現,2。
圖2. .Net Micro Framework上的非同步呼叫
Micro Framework的基本線程操作
下面先簡單介紹幾種最基本的,在.Net Micro Framework被支援的線程相關方法:
1. Join
和完整版的.Net Framework一樣Micro Framework的System.Threading.Thread類提供了Join方法,所謂join(合并)用於使當前線程等待直至調用該線程的方法執行完畢或者到達指定的等待時間,這裡不做多的介紹了。
2. Timer
這裡是說System.Threading.Timer類,和完整版的.Net Framework一樣它的建構函式中提供了一個TimerCallback委託類型的參數。它告訴線程池要拿出一個線程來按特定的時間或者頻率執行這個callback函數。
3. Event
Micro Framework中線程可以用事件的方式來響應, 比如在訪問一些共用資源的時候可以使用AutoResetEvent, 通過Wait-Set的組合來同步線程。
以上幾種基本操作,在SDK的Threading常式中都有使用,這裡不再贅述。
( Microsoft .NET Micro Framework\Samples\Threading )
WPF與Dispatcher
初次接觸.Net Micro Framework那WPF風格的UI編程模型時(沒有Windows Forms),總是面臨著許多效能和安全上的問題。其中很常見的就是資料更新和介面重新整理的問題。
典型的有UI的Micro Framework應用程式都會有兩個邏輯線程,一個是開發人員顯式建立用來處理硬體I/O的。另一個是由TinyCLR隱式建立並維護的,它用來處理所有的UI操作,我們姑且把it叫做WPF UI線程例如繪製UI元素,繪製控制項和表單等。
Micro Framework中的UI元素的更新,可以使用Dispacther和DispatcherTimer以安全執行緒的方式訪問UI元素。什麼是Dispacther呢?你可以把它看作綁定在上述第二個線程上的一個訊息佇列,WPF UI線程一直盯著這個隊列來接受各種操作命令。你只需要把你的命令,即相關待執行的函數,enqueue到這個隊列。即可讓這個函數獲得安全執行緒的執行。
下面我們通過一個時鐘的例子來說明如何在Micro Framework中使用Dispatcher,在本例中我們會在一條單獨的線程上更新畫面上的文本。開啟Visual Studio建立一個Micro Framework的windows程式
首先,在main函數之外,我們定義一個文本和負責更新它的線程:
private Text text;
private Thread updateThread;
然後,我們需要為這個更新操作定義一個委託,這個委託的執行個體將被用來添加到WPF UI的“訊息佇列”中去:
/// <summary>
/// 用來更新文本的委託
/// </summary>
/// <param name="newText">新文本</param>
public delegate void UpdateTextDelegate(String newText);
然後我們需要一個該委託的執行個體指向的實際函數,它非常簡單:
public void UpdateText(String newText)
{
text.TextContent = newText;
}
接下來我們還需要讓UpdateThread做點什麼,就是要它通過Dispatcher來完成這個非同步安全執行緒的UI更新:
public void UpdateTextThread()
{
while (true)
{
this.Dispatcher.BeginInvoke(new UpdateTextDelegate(UpdateText),
new object[] { DateTime.Now.ToString("hh:mm:ss") });
// 休眠1秒
Thread.Sleep(1000);
}
}
最後,我們要在CreatWindow方法返回前,添加如下代碼以啟動上述線程:
updateThread = new Thread(new ThreadStart(UpdateTextThread));
updateThread.Start();
其運行效果如下:
注意這裡我為了能讓大家看清楚,使用了較大的字型,如何為MF添加自訂的字型請參考:
http://www.cnblogs.com/fox23/archive/2008/10/20/customized-fonts-in-net-micro-framework.html
這種有周期的調用方式,還可以使用DispatcherTimer來處理,此時唯一需要改變的是UpdateText的簽名,使之符合EventHander的格式:
public void UpdateText(object sender, EventArgs e)
{
text.TextContent = DateTime.Now.ToString("hh:mm:ss");
}
使用DispatcherTimer非常簡單:
dispatchTimer = new DispatcherTimer(textView.Dispatcher);
dispatchTimer.Tick += new EventHandler(UpdateText);
dispatchTimer.Interval = new TimeSpan(0, 0, 1);
dispatchTimer.Start();
Micro Framework並不勝任所有情況
對於一些高輸送量,且對即時性要求比較高的情景(比如一個需要對音頻資料流編解碼輸出CD音質的裝置),使用Micro Framework裝置做這些工作並不是一個好的選擇。要滿足這樣的需求,你可以使用一些輔助的處理器(比如DSP),通過SPI或者I2C串連到Micro Framework裝置。讓它們去做繁重的資料處理工作,而使用Micro Framework來建立友好的UI並承擔一些非嚴格即時性的工作。
另外一種方式就是把Micro Framework移植到一個多線程即時作業系統上,然後把那些即時性高的代碼交給一條高優先順序的線程去跑。
總結
NET Micro Framework 將 .NET 的可靠性和效率與 Visual Studio的高生產率結合起來,以針對價格較低、資源受限的小型裝置開發應用程式,可協助人們使用熟悉的 Visual Studio 工具來構建託管的嵌入式應用程式。從中你可以發現使用Managed 程式碼以OO的方式在嵌入式裝置上面編寫擁有漂亮的UI的多線程程式是如此簡單自然。也許你再也不想回到過去那Win32或者POSIX風格的代碼中去了。同時要注意Micro Framework的適用範圍,注意避免由MF直接承擔一些即時性較高的,大資料量的任務。