新一代嵌入式微框架.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添加自定义的字体请参考:
?1247102571
这种有周期的调用方式,还可以使用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 工具来构建托管的嵌入式应用程序。从中你可以发现使用托管代码以OO的方式在嵌入式设备上面编写拥有漂亮的UI的多线程程序是如此简单自然。也许你再也不想回到过去那Win32或者POSIX风格的代码中去了。同时要注意Micro Framework的适用范围,注意避免由MF直接承担一些实时性较高的,大数据量的任务。