.NET的Windows Mobile本机线程同步(译文)
By S.F.
本文链接 https://www.kyfws.com/news/windows-mobile-native-thread-synchronization-for-n/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 10 分钟阅读 - 4672 个词 阅读量 0.NET的Windows Mobile本机线程同步(译文)
原文地址:https://www.codeproject.com/Articles/30664/Windows-Mobile-Native-Thread-Synchronization-for-N
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
一篇文章介绍了Windows Mobile支持的本机同步对象,并说明了如何使用它们.
介绍
在用于智能设备开发的MSDN论坛中,我遇到了一些要求提供指导的请求,这些指导要求哪些本机同步对象将成为解决方案的一部分.在多次遇到这些问题之后,我已经在Windows Mobile设备上编写了本机事件简介.本文是专门为托管代码开发人员编写的,尽管本机代码开发人员在解释本文中的代码示例时不应遇到挑战.
什么是同步对象?为什么需要它们?
同步对象用于协调多个线程执行的操作.以最简单的形式,同步对象将用于确保多个线程不会同时更改同一对象,尽管它们也可用于促进程序之间的通信. .NET Compact Framework包含许多可用于线程级同步的托管类,这些类可在"线程"名称空间中找到. 这些类在同一进程的上下文中工作,并且用于协调该进程中线程的动作.我在这里讨论的大多数操作系统提供的功能都可以跨进程边界工作.在这里,我将不讨论托管同步类,而仅讨论本机同步功能.在撰写本文时,.NET框架不直接支持操作系统提供的同步对象,因此我们将需要使用P/Invoke来访问它们.我将介绍以下对象: 我已经将对本机同步对象的本机函数调用包装到单独的类中.抽象类" SyncBase"包含此处定义的所有三个同步对象共有的功能.三个同步类中的每一个都从该抽象类派生.请注意,这些类实现了" IDisposable".这些类保留在Windows句柄(它们是系统资源)上,并且这些句柄的保留时间不应超过必需的时间.
尽管您可以随意使用此代码,但我强烈建议您研究MSDN中有关同步功能的文档.我对可用功能的使用并不详尽,并且以我认为是最常用的功能为目标.因此,还有其他一些同步功能具有的功能,我在这里不再赘述.
大事记
事件是最简单的同步对象.它们就像交通信号灯.等待事件的代码将暂停,直到事件变为信号状态为止.一个事件可以有一个名字.具有名称的事件可以在多个进程之间共享.由于.NET Framework中没有针对系统事件的本机支持,因此我们将需要使用P/Invoke与之交互. CreateEvent函数用于创建事件,并在CoreDLL中定义.事件可以是手动重置事件或自动重置事件.对于手动重置事件,一旦将事件设置为信号状态,它将一直保持该状态,直到使用[ResetEvent]将其显式更改为非信号状态为止.(http://msdn.microsoft.com/en -us/library/aa908992.aspx)函数.当事件处于信号状态时,尝试等待事件的每个线程将继续运行而不会被阻塞.对于自动重置,一旦将事件设置为已通知状态,它将一直保持该状态,直到线程等待它为止.一旦线程等待,它将被允许继续,并且该事件将自动重置回非信号状态.为了将事件暂时设置为信号状态,有一个名为" PulseEvent“的函数. PulseEvent的行为取决于是否将其用于手动重置或自动重置事件.当针对手动重置事件调用时,当前可以解除阻塞的所有线程都将解除阻塞,并且该事件将变回无信号状态.对于自动重置事件,将释放单个线程以使其被阻止,然后该事件返回到非信号状态. CreateEvent的调用签名在C ++标头中定义如下:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES
lpEventAttributes,
BOOL bManualReset,
BOOL InitialState,
LPTSTR lpName
);
调用它需要使用P/Invoke:
[DllImport("CoreDLL", SetLastError=true)]
public static extern IntPtr CreateEvent(
IntPtr AlwaysNull0,
[In, MarshalAs(UnmanagedType.Bool)] bool ManualReset,
[In, MarshalAs(UnmanagedType.Bool)] bool bInitialState,
[In, MarshalAs(UnmanagedType.BStr)] string Name
);
如果您尝试为SetEvent,PulseEvent和ResetEvent做出适当的P/Invoke语句,它们将全部失败.它们失败是因为所有Windows Mobile DLL中都不存在这些功能.如果查看名为” kfuncs.h"的C ++头文件,则会看到这些函数被定义为对" EventModify"的内联调用.调用EventModify
的第一个参数是我们正在使用的事件的句柄,第二个参数是一个数字值,指示对该事件要执行的操作.在我的代码中,我为" EventModify"做了一个P/Invoke语句,并声明了对它的" SetEvent"," ResetEvent"和" PulseEvent"的适当调用.
您几乎有足够的信息可以开始使用事件.最后一个缺失的部分是当不再使用事件句柄时必须执行的操作.当您不再需要事件时,可以通过调用[CloseHandle](http://msdn.microsoft.com/en-us/library/aa914720.aspx)
释放它.现在,转到我们的第一个代码示例.我已经在一个名为NativeSync的项目中声明了将用于访问上述功能的P/Invoke语句.如果打开CoreDLL.cs,则会找到" CreateEvent"," EventModify"和" CloseHandle“的P/调用.我还创建了一个名为SystemEvent
的类来包装与事件相关的函数.该类实现了” IDisposable",以便在不再使用该事件时可以释放该句柄.
第一个代码示例在名为" SoundOnEvent
“的项目中.该程序有两个线程.除了GUI线程外,方法SoundLoop''中还有一个带有入口点的线程,其定义如下.
SoundLoop``将等待事件并在发出事件信号时播放声音.托管该程序的形式只有一个按钮.该按钮的事件处理程序在另一个事件对象上调用Set事件.请注意,按钮的事件处理程序使用的"事件"对象的实例与” SoundLoop"方法中的实例不同.由于两个实例都是用相同的名称创建的,因此操作系统将为这两个对象实例赋予相同的内核对象.
SystemEvent _soundEvent;
private void Form1_Load(object sender, EventArgs e)
{
_soundEvent = new SystemEvent("PlaySound", false, false);
Thread t = new Thread(new ThreadStart(SoundLoop));
t.Start();
}
void SoundLoop()
{
using (SystemEvent evt = new SystemEvent("PlaySound", false, false))
{
while (_continuePlaying)
{
evt.Wait();
if (_continuePlaying)
{
SndPlaySync(SoundPath, 0);
}
}
}
}
private void cmdCreateEvent_Click(object sender, EventArgs e)
{
_soundEvent.SetEvent();
}
众所周知," while"循环看起来与antipattern相似,而[antipattern]经常discouraged,也称为忙碌的旋转或在变量上旋转.尽管代码看起来像忙碌的旋转,但事实并非如此.在忙碌的旋转中,不必要地重复了代码块,在燃烧CPU周期的同时不执行任何实际工作.在上面的代码中,线程完全暂停,直到需要工作为止,并且不会浪费CPU周期.
虽然不能将一个事件的句柄直接传递给另一个进程(一个句柄仅在特定进程的上下文中具有意义),但是如果另一个进程尝试使用相同的名称进行事件,它将收到自己的句柄以已经存在的对象.如果我们有另一个针对同一事件调用SetEvent
的程序,则第一个程序将对此做出反应(无需修改代码!).
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using NativeSync;
namespace SoundOnEventTrigger
{
public partial class Form1 : Form
{
SystemEvent _soundEvent = new SystemEvent("PlaySound", false, false);
public Form1()
{
InitializeComponent();
}
private void cmdCreateEvent_Click(object sender, EventArgs e)
{
_soundEvent.SetEvent();
}
private void miExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
_soundEvent.Dispose();
}
}
}
等待多个对象
当您有一组对象并希望等待它们中的任何一个发出信号时,请使用[WaitForMultipleObjects]方法(http://msdn.microsoft.com/zh-cn/library/ms687025(VS.85)). aspx).在Windows的桌面版本上,此功能可用于等待所有事件被发出信号,或等待任何一个事件被发出信号.对于Windows CE操作系统,此功能只能用于等待发生任何事件之一.该函数的本机原型如下:
DWORD WINAPI WaitForMultipleObjects(
__in DWORD nCount,
__in const HANDLE *lpHandles,
__in BOOL bWaitAll,
__in DWORD dwMilliseconds
);
第一个参数是传递的同步对象的数量,第二个参数是对象句柄的数组.第三个参数必须始终为" false"(在桌面操作系统上,如果第三个参数为" true",则该方法将等待所有要发出信号的对象).最后一个参数将是等待信号发送的对象的毫秒数,或者是无超时的-1.在我的代码中,我简化了调用签名,使其仅包含超时值和要等待的同步对象列表.该方法是在SyncBase类上实现的,因为系统事件不是程序可以等待的唯一对象类型(我们将在短期内介绍其他类型的对象). 演示" WaitForMultipleObjects"的示例代码与第一个示例相似.对于" WaitOnMultipleObjects"函数的包装,我让该方法返回对该对象的引用,该对象已发出信号并导致线程被解除阻塞.该示例程序将使用有关发出哪个事件的信息来决定播放哪种声音.用户可以通过界面上的三个按钮来发出三个系统事件对象之一的信号.还有一个第四事件,该事件无法通过该程序的UI上的按钮进行访问.第四个事件与第一个示例程序中使用的事件具有相同的名称,因此可以使用" SoundOnEventTrigger"来触发第四个事件.
void SoundLoop()
{
using (SystemEvent evt =
new SystemEvent("PlaySound", false, false))
{
int target;
while (_continuePlaying)
{
SystemEvent signaledEvent = SyncBase.WaitForMultipleObjects(
_event1,
_event2,
_event3,
_playSoundEvent
) as SystemEvent;
if (_continuePlaying)
{
target = 4;
if (signaledEvent == _event1)
target = 1;
if (signaledEvent == _event2)
target = 2;
if (signaledEvent == _event3)
target = 3;
SndPlaySync(String.Format(SoundPath, target), 0);
}
}
}
}
一旦了解了如何使用事件对象,就可以使用互斥量和信号量.它们都扩展了事件的概念.
互斥体
互斥锁用于确保只有一个线程可以访问资源.互斥锁处于发信号状态,直到线程等待它为止.当一个线程在等待互斥锁时,如果没有其他线程在等待该互斥锁,则该线程被授予该互斥锁的所有权,其等待函数将返回,并且该互斥锁将进入无信号状态,以确保没有其他线程在同一互斥锁上等待信号量可以继续.当后续线程尝试等待互斥对象时,它们将被阻止.使用互斥锁完成线程后,它可以调用ReleaseMutex,并且下一个被阻止的线程将被解除阻止并授予互斥锁的所有权.说明矩阵用法的程序有四个线程,它们只不过是计数.每个线程的计数显示在不同的文本框中.
下面给出了四个线程中的每个线程执行的源代码.我在代码中插入了对" Sleep"的调用,以使其运行更长的时间,以便您可以在线程执行时更轻松地观察用户界面.
void StartCounting()
{
int index = Interlocked.Increment(ref lastIndex);
using (NativeSync.Mutex m = new NativeSync.Mutex(MutexName,false))
{
mutex.Wait();
for (int i = 0; (i < 50) && (_continueRunning); ++i)
{
SetTextCount(index, i);
Thread.Sleep(100);
}
mutex.ReleaseMutex();
}
}
运行代码时,一次只执行一个计数器.一旦完成工作,下一个计数器将开始运行,因为互斥锁一次仅允许一个线程进入计数循环.
信号量
信号量与互斥量相似.它还限制了可以访问资源的线程数.与互斥锁不同,可以使用信号量来允许多个线程访问资源.信号量具有count属性.计数可以为0或更大,最大为创建信号量时确定的最大数量.当计数大于零时,信号量处于发信号状态.当线程等待信号量时,如果信号量处于信号状态(这意味着计数大于零),则计数将减少.使用资源完成线程后,应调用ReleaseSemaphore以增加计数. 为了演示信号量的用法,我采用了等待互斥量的相同程序,并对其进行了修改以改为等待信号量.此代码示例中使用的信号量最多允许两个线程对其进行访问.因此,运行该程序时,您将看到两个计数器同时工作.
下一步是什么
Windows Mobile操作系统提供了比此处所述更多的同步功能.在本文中,我只想专注于事件及其派生类.同步功能还支持进程间通信,监视终止过程以及许多其他功能. (请参阅MSDN 文档.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
.NET2.0 .NET3.0 WinMobile5 VS2008 C# WinMobile .NETCF .NET Visual-Studio Dev Intermediate WinMobile6 新闻 翻译