VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > c#编程 >
  • 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()会破坏程序,则几乎肯定会出现错误。


相关教程