使用 Netduino 和 .NET Microframework 进行多线程编程





5.00/5 (8投票s)
.NET Microframework 使使用 Netduino 进行多线程编程变得轻而易举。我们来看看如何做到。
首先,我们将使用“标准”方式。在我们的第一个示例中,我们将创建一个新线程并启动它。它将与主线程并行执行。
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
// Start the thread execution
someThread.Start();
// Main thread code continues
...
// Exiting main thread will stop someThread
}
private static void SomeThreadMethod()
{
// The thread's code
...
}
}
在第二个示例中,我们将执行线程并挂起主线程,直到我们恢复它。
public class Program
{
private static Thread mainThread;
public static void Main()
{
// Get handle to main thread
mainThread = Thread.CurrentThread;
Thread someThread = new Thread(SomeThreadMethod);
// Start the thread execution
someThread.Start();
// Suspend main thread execution
mainThread.Suspend();
//Following code will be executed after main thread is resumed
...
}
private static void SomeThreadMethod()
{
// The thread code
...
// Resume main thread
mainThread.Resume();
}
}
Thread 类包含一些有用的属性和方法。例如,使用 Priority 属性,我们可以控制线程相对于其他线程的调度方式。
第二个示例包含线程同步的元素。虽然,使用 Suspend()
和 Resume()
来同步线程仍然是可行的,但不建议使用它们。它们会导致一些问题,尤其是在共享线程之间的资源时。.NET Microframework 提供了一系列类来帮助我们避免这些问题。
例如,使用这些方法存在共享资源的问题。幸运的是,有几种方法可以解决这个问题。我们将不得不单独查看它们,因为它们解决了不同的问题。
起初,我们将查看一个方法属性 MethodImpl,更具体地说 MethodImplOptions 枚举。假设我们有一个方法,被多个线程调用,它执行一些独特的工作,并且必须完成其执行才能让下一个线程调用它。在这种情况下,我们可以像这样标记该方法:
[MethodImpl(MethodImplOptions.Synchronized)]
private void SomeSynchronizedMethod()
{
// Method's code
...
}
所有尝试访问此方法的线程将在该方法被其他线程使用期间被阻塞。
如果存在足够的情况可以证明这种方法是合理的,那么你可能会发现很多情况表明这种方法是完全不合适的。更好的方法是锁定方法共享的资源。为此,有一个名为 Monitor 的类。使用这个类很简单:
private static SomeThreadMethod()
{
...
try
{
Monitor.Enter(someSharedResource);
//access shared resource here
...
}
finally
{
Monitor.Exit(someSharedResource);
...
}
...
}
private static SomeOtherThreadMethod()
{
...
try
{
Monitor.Enter(someSharedResource);
//access shared resource here
...
}
finally
{
Monitor.Exit(someSharedResource);
...
}
...
}
现在我们可以并行执行这两个方法。当其中一个到达 Monitor.Enter()
时,它会获得对共享资源的访问权限,然后对其进行操作。此时,如果其他方法调用 Monitor.Enter()
来访问同一资源,它将被阻塞,直到第一个方法执行 Monitor.Exit()
来释放资源。请注意,Monitor.Exit()
位于异常处理程序的 finally 块中。这是最佳实践;否则,如果在方法中发生异常,资源可能会永远被锁定。
您可能知道,这等同于 C# 的 lock 关键字。当编译器遇到
lock(someSharedResource)
{
// access shared resource here
...
}
它会编译成
try
{
Monitor.Enter(someSharedResource);
//access shared resource here
...
}
finally
{
Monitor.Exit(someSharedResource);
}
Monitor 和 Thread 类是 System.Threading 命名空间的一部分。正如我之前提到的,还有其他几个类,每个类都为我们提供了不同的解决问题的方法。作为本部分的结论,我们将快速回顾一下如何使用 Interlocked 类及其 成员。它很简单:
public class Program
{
private static int someSharedResource;
...
private static void SomeThreadMethod()
{
//...
Interlocked.Increment(ref someSharedResource);
//...
}
private static void SomeOtherThreadMethod()
{
//...
Interlocked.Increment(ref someSharedResource);
//...
}
}
或
public class SharedResources
{
public static int SomeSharedResource;
}
public class Program
{
private static void SomeThreadMethod()
{
//...
Interlocked.Increment(ref SharedResources.SomeSharedResource);
//...
}
private static void SomeOtherThreadMethod()
{
//...
Interlocked.Decrement(ref SharedResources.SomeSharedResource);
//...
}
}
Exchange() 和 CompareExchange() 提供了一些有趣的可能性,所以花点时间阅读文档。
我们还没有完成多线程的主题,所以请准备好在下一部分中了解更多内容。
除了之前提到的同步线程的方法外,Microframework 还提供了两个使用事件的类来实现此目的:AutoResetEvent 和 ManualResetEvent。这两个类都继承自 WaitHandle,它们之间的唯一区别是使用 ManualResetEvent 时必须手动重置状态。让我们看一些使用这些类的示例:
public class Program
{
// Initialise auto reset event with not signalled (false) initial state
private static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main()
{
Thread someThread = new Thread(SomeMethod);
someThread.Start();
...
// Waiting for thread's notification
autoResetEvent.WaitOne();
...
// This is auto reset event so no reset is required
someThread.Start();
autoResetEvent.WaitOne();
...
}
private static void SomeMethod()
{
...
// Send notification
autoResetEvent.Set();
}
}
或
public class Program
{
// Initialise manual reset event with not signalled (false) initial state
private static ManualResetEvent namualResetEvent = new ManualResetEvent(false);
static void Main()
{
Thread someThread = new Thread(SomeMethod);
someThread.Start();
...
// Waiting for thread's notification
namualResetEvent.WaitOne();
...
// This is manual reset event so reset is required
namualResetEvent.Reset();
someThread.Start();
namualResetEvent.WaitOne();
...
}
private static void SomeMethod()
{
...
// Send notification
namualResetEvent.Set();
}
}
WaitHandle 类(因此 AutoResetEvent 和 ManualResetEvent 类也是如此)还有两个更有趣且更灵活的方法 – WaitAll() 和 WaitAny()。第一个等待指定数组中的所有元素收到信号,第二个等待其中任何一个元素收到信号。请注意,它们接受 WaithHandle
s 的数组,这样您就可以创建许多线程并在它们之间进行同步。这两种方法都有重载,允许指定等待的毫秒数以及指定线程是否应在等待之前退出同步域(如果处于同步域中),并在之后重新获取它。
您可以使用 ThreadPriority 枚举设置线程优先级,并使用 ThreadState 枚举检查线程状态。TheadState
允许您执行按位操作,因此您可以检查多种状态。
if (someThread.ThreadState == (ThreadState.Suspended | ThreadState.Stopped))
{
...
}
最后,我们不能忽略 Timer 类 :) 这是一个非常有用的类,“提供了一种在指定时间间隔执行方法的机制”。它就像它说的那样简单。Timer 的构造函数使用 TimerCallback 委托来指定您要执行的方法,并且有两个方便的重载,允许我们使用 int(以毫秒为单位)或 TimeSpan 来指定时间间隔。第二个参数在您想将附加信息传递给执行方法时也很有用。如果没有信息要传递,请将其保留为 null。
public class Program
{
static void Main()
{
// Initial delay 5s, invoke every 1 second after that
var someTimer = new Timer(new TimerCallback(SomeMethod), "Some data", 5000, 1000);
// Or
//var someTimer = new Timer(new TimerCallback(SomeMethod), "Some data", new TimeSpan(0, 0, 0, 5), new TimeSpan(0, 0, 0, 1));
...
Console.ReadLine();
}
private static void SomeMethod(object data)
{
...
Console.WriteLine((string)data);
...
}
}
Microframework 的最后一个好消息是我们可以使用 lambda 表达式
public class Program
{
static void Main()
{
var someTimer = new Timer(new TimerCallback(
(object data) =>
{
Console.WriteLine((string)data);
}),
"Some data", new TimeSpan(0, 0, 0, 5), new TimeSpan(0, 0, 0, 1));
...
Console.ReadLine();
}
}
最后一点需要注意的是,Microframework 的 System.Threading 命名空间并不实现完整 .NET Framework System.Threading 的所有类。
现在我想向您展示另一种同步线程的方法。它更像是“等待”一个线程完成,然后再继续执行调用线程中的代码。我见过人们通常这样做:
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
// Start the thread execution
someThread.Start();
// Put main thread into infinite sleep
Thread.Sleep(Timeout.Infinite);
// Main thread never resumes!
}
private static void SomeThreadMethod()
{
// The thread's code
...
}
}
好吧,这看起来可能还可以,而且工作起来也还可以,但总有些“丑陋”,并且您会失去对主线程继续执行的控制。.NETMF 提供了一种很好的方法来做到这一点:
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
// Start the thread execution
someThread.Start();
// Tell main tread to wait until someThread finish its execution
someThread.Join();
// Main thread code continues
...
}
private static void SomeThreadMethod()
{
// The thread's code
...
}
}
这下就好多了!您仍然可以阻止应用程序退出:只需从未返回 SomeThreadMethod()
。但随时可以更改算法,可以轻松做到。Join()
方法的另一个优点是您不必进行事件同步,从而为您节省了编写额外代码的麻烦。
这里还有一个非常快捷的方式,只是让某个线程去做某事:
public class Program
{
public static void Main()
{
new Thread(
() =>
Debug.Print("Hello ...")
) { Priority = ThreadPriority.AboveNormal }
.Start();
new Thread(
() =>
{
Debug.Print("Line 1");
Debug.Print("Line 2");
}) { Priority = ThreadPriority.Lowest }
.Start();
// Main thread code continues
...
}
private static void SomeThreadMethod()
{
// The thread's code
...
}
}