-
C#线程 入门-线程的概念
第一部分: 入门
介绍和概念
C#支持通过多线程并行执行代码。线程是一个独立的执行路径,能够与其他线程同时运行。C#客户端程序(控制台,WPF或Windows窗体)在CLR和操作系统自动创建的单个线程(“主”线程)中启动,并通过创建其他线程而成为多线程。这是一个简单的示例及其输出:
所有示例均假定导入了以下名称空间:
using System;
using System.Threading;
class ThreadTest { static void Main() { Thread t = new Thread (WriteY); // Kick off a new thread t.Start(); // running WriteY() // Simultaneously, do something on the main thread. for (int i = 0; i < 1000; i++) Console.Write ("x"); } static void WriteY() { for (int i = 0; i < 1000; i++) Console.Write ("y"); } }
xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
...
主线程创建一个新线程t,在该线程上运行一种方法,该方法反复打印字符“ y”。同时,主线程重复打印字符“ x”:
一旦启动,线程的IsAlive属性将返回true,直到线程结束为止。当传递给线程构造函数的委托完成执行时,线程结束。一旦结束,线程将无法重新启动。
1 static void Main() 2 { 3 new Thread (Go).Start(); // Call Go() on a new thread 4 Go(); // Call Go() on the main thread 5 } 6 7 static void Go() 8 { 9 // Declare and use a local variable - 'cycles' 10 for (int cycles = 0; cycles < 5; cycles++) Console.Write ('?'); 11 }
??????????
在每个线程的内存堆栈上创建一个单独的cycles变量副本,因此,可以预见的是,输出为十个问号。
如果线程具有对同一对象实例的公共引用,则它们共享数据。例如:
class ThreadTest { bool done; static void Main() { ThreadTest tt = new ThreadTest(); // Create a common instance new Thread (tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { done = true; Console.WriteLine ("Done"); } } }
由于两个线程在同一个ThreadTest实例上调用Go(),因此它们共享done字段。这导致“完成”打印一次而不是两次:
完成
静态字段提供了另一种在线程之间共享数据的方法。这是同一示例,其作为静态字段完成了:
class ThreadTest { static bool done; // Static fields are shared between all threads static void Main() { new Thread (Go).Start(); Go(); } static void Go() { if (!done) { done = true; Console.WriteLine ("Done"); } } }
这两个示例都说明了另一个关键概念:线程安全的概念(或更确切地说,缺乏安全性)。输出实际上是不确定的:“完成”有可能(尽管不太可能)打印两次。但是,如果我们在Go方法中交换语句的顺序,则两次打印完成的机率会大大提高:
static void Go() { if (!done) { Console.WriteLine ("Done"); done = true; } }
完成
完成(通常!)
问题在于,一个线程可以评估if语句是否正确,而另一个线程正在执行WriteLine语句-在有机会将done设置为true之前。
补救措施是在读写公共字段时获得排他锁。 C#为此提供了lock语句:
class ThreadSafe { static bool done; static readonly object locker = new object(); static void Main() { new Thread (Go).Start(); Go(); } static void Go() { lock (locker) { if (!done) { Console.WriteLine ("Done"); done = true; } } } }
当两个线程同时争用一个锁(在这种情况下为锁柜)时,一个线程将等待或阻塞,直到锁可用为止。在这种情况下,可以确保一次只有一个线程可以输入代码的关键部分,并且“完成”将仅打印一次。以这种方式受到保护的代码(在多线程上下文中不受不确定性的影响)被称为线程安全的。共享数据是造成多线程复杂性和模糊错误的主要原因。尽管通常是必不可少的,但保持尽可能简单是值得的。线程虽然被阻止,但不会消耗CPU资源。
Join and Sleep
您可以通过调用其Join()来等待另一个线程结束。例如:
static void Main() { Thread t = new Thread (Go); t.Start(); t.Join(); Console.WriteLine ("Thread t has ended!"); } static void Go() { for (int i = 0; i < 1000; i++) Console.Write ("y"); }
这将打印“ y” 1,000次,然后显示“线程t已结束!”。紧接着。您可以在调用Join时包含一个超时(以毫秒为单位)或作为TimeSpan。然后,如果线程结束,则返回true;如果超时,则返回false。
Thread.Sleep将当前线程暂停指定的时间:
1
2
|
Thread.Sleep (TimeSpan.FromHours (1)); // sleep for 1 hour Thread.Sleep (500); // sleep for 500 milliseconds |
在等待睡眠或加入时,线程被阻塞,因此不消耗CPU资源。
Thread.Sleep(0)立即放弃线程的当前时间片,自动将CPU移交给其他线程。 Framework 4.0的新Thread.Yield()方法具有相同的作用-只是它只放弃运行在同一处理器上的线程。
Sleep(0)或Yield在生产代码中偶尔用于进行高级性能调整。它也是帮助发现线程安全问题的出色诊断工具:如果在代码中的任意位置插入Thread.Yield()会破坏程序,则几乎肯定会出现错误。