65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2014 年 7 月 11 日

CPOL

5分钟阅读

viewsIcon

26300

.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 还提供了两个使用事件的类来实现此目的:AutoResetEventManualResetEvent。这两个类都继承自 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 类(因此 AutoResetEventManualResetEvent 类也是如此)还有两个更有趣且更灵活的方法 – WaitAll()WaitAny()。第一个等待指定数组中的所有元素收到信号,第二个等待其中任何一个元素收到信号。请注意,它们接受 WaithHandles 的数组,这样您就可以创建许多线程并在它们之间进行同步。这两种方法都有重载,允许指定等待的毫秒数以及指定线程是否应在等待之前退出同步域(如果处于同步域中),并在之后重新获取它。

您可以使用 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
          ...
     }
}
© . All rights reserved.