Threading in C#

来源:互联网 发布:ubuntu获取root权限 编辑:程序博客网 时间:2024/05/29 16:56

Threading in C#
线程,很复杂的一个东西。没有写过相关的代码,没有发言权。
所以只了解一下CLR下线程的基本知识点。

一、线程基本概念
1.CLR为每个线程保留独立的内存栈,从而分离各个线程的局部变量。

2.各个线程可以共享静态变量,以及所引用的同一个类实例的成员。

3.线程一旦结束,就不能再被执行了。

4.传递参数给线程。大概有三种方式:
A.使用ParameterizedThreadStart
public delegate void ParameterizedThreadStart(object obj);
static void Main()
{
 Thread t = new Thread(new ParameterizedThreadStart(Go));
 t.Start(true);
}
static void Go(object b)
{
 bool v = (bool)b;
}

这种方式只能传一个参数。

B.使用匿名代理,间接调用其他方法
static void Main()
{
 Thread t = new Thread(delegate() { LocalFunc("local function"); }
 t.Start();
}
static void LocalFunc(string text) { Console.WriteLine(text);}

这种方式可以传任意个数的参数。
但是这种方式下,参数的传递不是在Start(),而是在callback里。这样参数就有可能在Start()之前被任意篡改。
static void Main()
{
 string text = "Initial";
 Thread t = new Thread(delegate() { LocalFunc(text); }
 text = "Changed";
 t.Start();
}
所以最好把要传递的参数设置为常量。(第一种方式和第三种方式其实都存在这种问题。)

C.代理方法使用类实例方法,把类实例的成员变量作为参数。
static void Main()
{
 ClassA a = new ClassA();
 a.size = 100;
 Thread t = new Thread(a.Test());
 t.Start();
}

class ClassA
{
 public int size = 0;
 public void Test()
 {
  Console.WriteLine(size.toString());
 }
}

5.可以给线程的名字赋值,但是只能赋值一次。赋值多于一次会发生Exception。

6.线程分为Foreground(前台)和Background(后台)两种。
  线程默认被创建为Foreground。
  对于程序来说,当所有的Foreground线程结束了,那么这个程序也就结束了,即使还有Background线程在运行。因此           Background线程上的finally代码并不一定能在程序退出之前执行(所以要使用join方法阻止主线程的直接退出)。

7.有时候我们发现,windows程序有时候显示已经关闭,但是在process列表里还能看到。这就是因为windows form的主线程虽然   已经结束,但是还有其他的Foreground的线程仍在运行。

8.异常处理
Application.ThreadException,这个事件可以捕获到主线程上在windows消息队列建立起来以后的所有异常。但是无法捕获到其他线程上的异常。
AppDomain.UnhandledException,这个事件可以捕获到AppDomain上所有线程上的异常,但是它无法阻止程序的crash。因此对于一个production,应该在每个线程的入口点添加异常处理,避免production上出现crash。

9.Thread.Sleep(0)
Sleep(int millionsecond)是指放弃cpu一段时间。Sleep(0)并不是不放弃cpu,实际上Sleep(0)还是要放弃cpu,虽然时间很短,但是足够其他alive的线程获得cpu的时间片。

10.Join(int timeout)
Join方法可以block当前线程从而等待另外一个线程。有种说法:
JOin虽然block,但是它不影响消息队列的正常运行;Sleep()则会将消息队列挂起。
但是我做了个测试好像都会把消息队列挂起来。

11.ThreadPool中的所有线程都是Background线程。

二、EventWaitHandle, Mutext和Semaphore都继承自WaitHandle。他们都可以用来做多线程的同步,并且他们都支持跨进程的同步。 1.AutoResetEvent vs. ManualResetEvent
这两个都是EventWaitHandle的子类。都可以用来lock某个线程。他们只是Reset的方式区别不同:
AutoResetEvent在每次Set后,会自动Reset,从而保证每次只能有1个线程被唤醒。
ManualResetEvent则在每次Set后,保持信号为被通知,直到被手动的Reset,从而可以每次唤醒多个线程。

2.Mutex
Mutex其实就是个lock,只是它支持跨进程的lock。它最常用的方式是保证每个操作系统上只能运行某一个程序的实例。
class OneAtATimePlease
{
 // Use a name unique to the application (eg include your company URL)
 static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

 static void Main()
 {
  // Wait 5 seconds if contended – in case another instance
  // of the program is in the process of shutting down.
  if (!mutex.WaitOne (TimeSpan.FromSeconds (5), false))
  {
   Console.WriteLine ("Another instance of the app is running. Bye!");
   return;
  }

  try
  {
   Console.WriteLine ("Running - press Enter to exit");
   Console.ReadLine();
  }
  finally { mutex.ReleaseMutex(); }
 }
}
只要在Mutex初始化时给Mutex一个name,它就可以被跨进程使用了。因为Mutex只能被一个线程占用,直到它被release。
如果创建Mutext的程序还在运行,那么当另外一个程序调用waitone时就会得到false。

3。Semaphore
信号量,是更高级的Mutex,因为它可以规定可以lock的线程的个数。
class SemaphoreTest
{
 static Semaphore s = new Semaphore (3, 3); // Available=3; Capacity=3
 
 static void Main()
 {
  for (int i = 0; i < 10; i++) new Thread (Go).Start();
 }
 
 static void Go()
 {
  while (true)
  {
   s.WaitOne();
   Thread.Sleep (100); // Only 3 threads can get here at once
   s.Release();
  }
 }
}
上面的程序可以保证只有3个线程可以同时运行。如果用WaitHandle实现就复杂了。