C# 开始一个没有c 结束线程的线程会怎样?

所有回答(3)
参考官方提示&&&默认是不支持的
园豆:12272
你可以控制线程的执行流程。
去看看 autoreset 这些相关类。
你的疑问是正确的,你的问题也不白痴,呵呵。
线程池就是有一些限制的,所以并不是每种情况都适合用线程池。
如果你需要一个一直运行的线程、或者要对线程做一些控制&&很遗憾,不能用ThreadPool&&
&&&您需要以后才能回答,未注册用户请先。C#中的线程 -- 线程入门 - 淡如水wp - 博客园
posts - 70, comments - 141, trackbacks - 0, articles - 0
内容预告:
线程入门(线程概念,创建线程)
同步基础(同步本质,线程安全,线程中断,线程状态,同步上下文)
使用线程(后台任务,线程池,读写锁,异步代理,定时器,本地存储)
高级话题(非阻塞线程,扶起和恢复)
C#支持通过多线程并行地执行代码,一个线程是独立的执行个体,可以和其他线程同时运行。
CLR和操作系统会给C#程序开启一个线程(主线程),可以被用来作为创建多线程的起点,例子:
class ThreadTest {
static void Main() {
Thread t = new Thread (WriteY);
t.Start(); // Run WriteY on the new thread
while (true) Console.Write ("x"); // Write 'x' forever
static void WriteY() {
while (true) Console.Write ("y"); // Write 'y' forever
运行结果将是:
xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
主线程创建了一个线程 t ,执行了重复输出y的操作,主要线程执行了重复输出x的操作。CLR给每个线程分配了单独的线程栈,所以本地变量是每个线程单独保存的,下面的例子,我们用一个本地变量定义一个函数,然后在main函数和新的线程里同时执行这个函数
static void Main() {
new Thread (Go).Start(); // Call Go() on a new thread
Go(); // Call Go() on the main thread
static void Go() {
// Declare and use a local variable - 'cycles'
for (int cycles = 0; cycles & 5; cycles++) Console.Write ('?');
执行结果:
??????????
每个线程的内存栈里都创建了一个单独的变量cycle,所以输出是10个?如果是引用同一个对象的话,线程则共享这个数据:
class ThreadTest {
static void Main() {
ThreadTest tt = new ThreadTest(); // Create a common instance
new Thread (tt.Go).Start();
// Note that Go is now an instance method
void Go() {
if (!done) { done = true; Console.WriteLine ("Done"); }
因为两个线程都调用Go(),它们共享done这个变量,所以done只输出一次:
static变量提供一种不同的方式在线程中共享变量,这里是一个例子:
class ThreadTest {
static bool // Static fields are shared between all threads
static void Main() {
new Thread (Go).Start();
static void Go() {
if (!done) { done = true; Console.WriteLine ("Done"); }
这里输出就不太确定了,看起来要输出两次done,其实不可能。我们交换一下Go的次序,
class ThreadTest {
static bool // Static fields are shared between all threads
static void Main() {
new Thread (Go).Start();
static void Go() {
if (!done) { Console.WriteLine ("Done"); done = true; }
done就有可能输出两次。因为在一个线程计算if表达式然后执行Console.WriteLine ("Done");的时候,另一个线程可能有机会在done的值改变之前先输出done。其实在C#中可以用lock来达到这个目的:
class ThreadSafe {
static bool
static object locker = new object();
static void Main() {
new Thread (Go).Start();
static void Go() {
lock (locker) {
if (!done) { Console.WriteLine ("Done"); done = true; }
当两个线程同时竞争一个锁时,一个线程等待,或者说阻塞,直到锁空出来。这主要是保证同时只能有一个线程可以进入临界代码区域,"Done"只会被输出一次。代码是以这样的方式被保护的,来自于多线程上下文的不确定性,叫做线程安全。临时地暂停,或阻塞,是线程同步的基本功能。如果一个线程想要暂停,或者休眠一段时间,可以用:
Thread.Sleep (TimeSpan.FromSeconds (30)); // 阻塞30秒
一个线程可以通过调用Join等待另一个线程结束:
Thread t = new Thread (Go); // 假设Go是静态函数。
t.Start();
Thread.Join (t); // 阻塞,只到线程t结束。
线程如何工作:在内部,多线程是被线程调度器管理的,是CLR代替操作系统干的活。线程调度器要保证所有活跃线程合理分配执行时间,以及在等待中的线程(这些线程是不消耗CPU时间的)。在单核机器上,线程调度是以在活跃线程间快速切换时间片的方式工作的。就像就第一个例子,重复输出x或y的线程轮换得到时间片。在Windows XP下,一个时间片就是几十毫秒,这还是要比CPU在线程间切换能干更多事,一次线程切换也就几毫秒的事。在多核机器上,多线程的实现是结合了时间片轮换和并发,并发是不同的线程同时运行在不同的CPU上,因为机器要运行的线程数远远大于CPU的数量,所以还需要时间片切换。线程不能控制自己什么时候执行,完全由操作系统的时间片切换机制来控制。
线程和进程:
总是有面试官喜欢把线程和进程做比较,其实两者根本不是一个级别的东西。一个单独的应用程序内所有的线程都在逻辑或属于一个进程的。进程:一个运行应用程序的操作系统单元。线程与进程有些相似之处,比如:对于实例,进程和线程都是典型的时间片轮换的执行机制。关键的不同点在于进程间是相互独立的,而同一个应用程序里的线程间是共享堆内存的,这也是性能的用武之地:一个线程可以在后台运行,另一个线程可以显示得到的数据。
什么时候应该用多线程:
一个普通的多线程程序在后台运行耗时的任务时。主线程保持运行状态,工作线程干后台的活。在Windows Form程序里,如果主线程被长时间占用,键盘和鼠标的操作就不能处理了,然后程序就变成&无响应&了。所以,需要把耗时的任务放在后台运行,让主线程保证响应用户输入。
在非UI程序中,比如Windows服务,多线程就特别有用了,当等待另一台机器(例如一个应用服务器,数据库服务器,客户端)的响应时,用一个工作线程来等待,让主线程保持畅通。
多线程的另一个用处是在函数中有大量计算时,函数划成多个线程可以在多核的机器上执行更快(可以用Environment.ProcessorCount得到CPU核心数量)。
一个C#程序可以通过两种方式成为多线程:显示地创建线程,或者使用.NET显示创建线程的功能(比如BackgroundWorker,线程池,定时器,远程服务器,WebSerivce或ASP.NET程序),在后面这些情况下,只能是多线程。单线程的web服务器肯定不行。在无状态的web服务器里,多线程是相当简单的。主要的问题是如果处理缓存数据的锁机制。
什么时候不应该用多线程:
多线程也有缺点,最大的问题是会让程序变得复杂,多线程本身并不复杂,复杂在于线程间的交互。能让开发周期变长,以及Bug变多。所以需要把多个线程间的交互设计的尽量简单,或者就别用多线程,除非你可以保证的很好。
过多地在线程间切换和分配内存栈,也会带来CPU资源的消耗,通常,当硬盘IO很多时,只有一两个线程依次执行任务的程序性能要更好,而多个性能同时执行一个任务的性能不怎么样。后面会讨论生产者/消费者模型。
创建和启动线程:
&可以用Thread类的构造函数创建线程,传递一个ThreadStart的代理作为参数,这个代理指向将要执行的函数,以下是这个代理的定义:
public delegate void ThreadStart();
执行Start()函数,线程即开始运行,在函数结束后线程会返回,下面是创建ThreadStart的C#语法:
class ThreadTest {
static void Main() {
Thread t = new Thread (new ThreadStart (Go));
t.Start(); // Run Go() on the new thread.
Go(); // Simultaneously run Go() in the main thread.
static void Go() { Console.WriteLine ("hello!"); }
线程t执行Go函数,同时主线程也调用Go,执行结果是:
也可以用C#的语法糖:编译器会自动创建一个ThreadStart的代理。
static void Main() {
Thread t = new Thread (Go); // No need to explicitly use ThreadStart
t.Start();
static void Go() { ... }
还有更简单的匿名函数语法:
static void Main() {
Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); });
t.Start();
给ThreadStart传递参数:这种形式只能传递一个参数
public delegate void ParameterizedThreadStart (object obj);
class ThreadTest {
static void Main() {
Thread t = new Thread (Go);
t.Start (true); // == Go (true)
Go (false);
static void Go (object upperCase) {
bool upper = (bool) upperC
Console.WriteLine (upper ? "HELLO!" : "hello!");
如果用匿名函数方式:可以传递多个参数,且也不需要类型转换,
static void Main() {
Thread t = new Thread (delegate() { WriteText ("Hello"); });
t.Start();
static void WriteText (string text) { Console.WriteLine (text); }
还有一种传参的方式是传一个实例过去,而不是传一个静态函数:
class ThreadTest {
static void Main() {
ThreadTest instance1 = new ThreadTest();
instance1.upper = true;
Thread t = new Thread (instance1.Go);
t.Start();
ThreadTest instance2 = new ThreadTest();
instance2.Go(); // Main thread & runs with upper=false
void Go() { Console.WriteLine (upper ? "HELLO!" : "hello!"); }
线程命名:线程有一个Name属性,在调试时很有用。
class ThreadNaming {
static void Main() {
Thread.CurrentThread.Name = "main";
Thread worker = new Thread (Go);
worker.Name = "worker";
worker.Start();
static void Go() {
Console.WriteLine ("Hello from " + Thread.CurrentThread.Name);
Hello from main
Hello from worker
前台线程和后台线程:
默认情况下,线程都是前台线程,意味着任何一个前台线程正在运行,程序就是运行的。而后台线程在所有前台线程终止时也会立即终止。
把线程从前台改为后台,线程在CPU调度器的优先级和状态是不会改变的。
class PriorityTest {
static void Main (string[] args) {
Thread worker = new Thread (delegate() { Console.ReadLine(); });
if (args.Length & 0) worker.IsBackground = true;
worker.Start();
如果这个程序执行时不带参数,worker线程默认是前台线程,并且会在ReadLine这一行等着用户输入。同时,主线程退出,但是程序会继续运行,因为ReadLine也是前台线程。如果传了一个参数给Main函数,worker线程的状态则被设置成后台状态,程序几乎会在主线程结束时立即退出--终于ReadLine。当后台线程以这种方式终止时,任何代码都不再执行了,这种代码是不推荐的,所以最好在程序退出前等待所有后台线程,可以用超时时间(Thread.Join)来做。如果因为某些原因worker线程一直不结束,也能终止这个线程,这种情况下最好记录一下日志来分析什么情况导致的。
在Windows Form中被抛弃的前台线程是个潜在的危险,因为程序在主线程结束将要退出时,它还在继续运行。在Windows的任务管理器里,它在应用程序Tab里会消失,但在进程Tab里还在。除非用户显式地结束它。常见的程序退出失败的可能性就是忘记了前台线程。
线程的优先级:线程的优先级决定了线程的执行时间。
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
线程的优先级为Highest时,并不意味着线程会实时运行,要想实时运行,进程的优先级也得是High。当你的进程的优先级是High时,如果程序进入了死循环,系统会死锁。这个时候就只有按电源键了。所以,慎用。
最好将实时线程和UI分开在两个线程,并设置成不同的优先级,通过远程或共享内存通信,共享内存需要P/Invoking Win32 API(CreateFileMapping和MapViewOfFile)。
线程的异常处理:线程一旦启动,任何在try/catch/finally范围内创建线程的代码块与try/catch/finally就没有什么关系了。
public static void Main() {
new Thread (Go).Start();
catch (Exception ex) {
// We'll never get here!
Console.WriteLine ("Exception!");
static void Go() { throw null; }
上例中的try/catch基本没用了,新创建的线程可能是未处理的空引用异常,最好在线程要执行的代码里加异常捕获:
public static void Main() {
new Thread (Go).Start();
static void Go() {
throw null; // this exception will get caught below
catch (Exception ex) {
Typically log the exception, and/or signal another thread
that we've come unstuck
从.NET2.0开始,线程上任何未处理的异常会导致整个程序挂掉,意味着千万别忽略异常,在线程要执行的函数里,给每个可能异常的代码加上try/catch。这可能有点麻烦,所以,很多人这样处理,用全局的异常处理:
using System.T
using System.Windows.F
static class Program {
static void Main() {
Application.ThreadException += HandleE
Application.Run (new MainForm());
static void HandleError (object sender, ThreadExceptionEventArgs e) {
Log exception, then either exit the app or continue...
Application.ThreadException事件会在代码抛出异常时被触发,这样看起来很完美--可以捕获所有异常,但在worker线程上的异常可能捕获不了,在main函数里的窗体的构造函数,在Windows的消息循环之前就执行了。.NET提供了一个低层的事件捕获全局异常:AppDomain.UnhandledException,它才可以捕获所有异常(UI和非UI的)。虽然它提供了一个很好的方式捕获所有异常并记录异常日志,但是它没有办法阻止程序关系,也没有办法阻止.NET的异常对话框。c# 多线程问题,如何终止特定线程
[问题点数:40分,结帖人dongfangshang]
c# 多线程问题,如何终止特定线程
[问题点数:40分,结帖人dongfangshang]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关推荐:
2002年10月 VC/MFC大版内专家分月排行榜第一2004年1月 软件工程/管理大版内专家分月排行榜第一2003年1月 软件工程/管理大版内专家分月排行榜第一
2002年10月 VC/MFC大版内专家分月排行榜第一2004年1月 软件工程/管理大版内专家分月排行榜第一2003年1月 软件工程/管理大版内专家分月排行榜第一
本帖子已过去太久远了,不再提供回复功能。定义一个委托实现回调函数
public delegate void CallBackDelegate(string message);
程序开始的时候
//把回调的方法给委托变量
CallBackDelegate cbd = CallB
//启动线程
Thread th = new Thread(Fun);
th.Start(cbd);//开始线程,代入参数
//线程执行的方法 参数是个委托, 线程中参数需要用object类型.
public void Fun(object o)
//这里是你的操作代码,循环,根据条件退出while
while(true)
//把传来的参数转换为委托
CallBackDelegate cbd = o as CallBackD
//执行回调.
cbd(&这个线程传回的信息&);
//回调方法
private void CallBack(string message)
//主线程报告信息,可以根据这个信息做判断操作,执行不同逻辑.
MessageBox.Show(message);
比如要在刚加载的时候进行网络请求操作,如果直接在UI线程中执行操作的话,那么界面就会是一片空白,然后出现程序没有响应的状态,用户容易以为是程序死了。如果只是在后台开一个线程然后在该线程中执行操作的话,界面虽然不是一片空白,但是用户无法做其他操作,如果点击某个按钮,那么还会出现程序没有响应的状态。
而如果用上面的回调方法的话,就可以解决上面的问题,所以如果有比较耗时的操作,那么使用回调是一个不错的选择
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:179755次
积分:2524
积分:2524
排名:第7695名
原创:32篇
转载:256篇
评论:37条
(14)(8)(10)(2)(34)(14)(9)(15)(25)(3)(1)(1)(1)(5)(3)(7)(14)(7)(8)(1)(3)(5)(17)(3)(15)(6)(6)(16)(11)(1)(19)(4)C#编程总结(三)线程同步 - 停留的风 - 博客园
posts - 214, comments - 1142, trackbacks - 5, articles - 27
C#编程总结(三)线程同步
在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。
"如果觉得有用,请帮顶! 如果有不足之处,欢迎拍砖!"
线程同步的方式
& & &线程同步有:临界区、互斥区、事件、信号量四种方式  临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别   1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。   2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享   3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目   4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
C#中常见线程同步方法
我们介绍几种常用的C#进行线程同步的方式,这些方式可以根据其原理,找到对应上面的四种类型之一。
1、Interlocked为多个线程共享的变量提供原子操作。
根据经验,那些需要在多线程情况下被保护的资源通常是整型值,且这些整型值在多线程下最常见的操作就是递增、递减或相加操作。Interlocked类提供了一个专门的机制用于完成这些特定的操作。这个类提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。 此类的成员不引发异常。
Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。 在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
1)将实例变量中的值加载到寄存器中。2)增加或减少该值。3)在实例变量中存储该值。如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。 然后由另一个线程执行所有三个步骤。 当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。&
Exchange 方法自动交换指定变量的值。 CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。 比较和交换操作按原子操作执行。
案例分析:共享打印机。
通常我们会使用共享打印机,几台计算机共享一台打印机,每台计算机可以发出打印指令,可能会出现并发情况。当然我们知道,打印机采用了队列技术。为了简化操作,我们假定,在打印机收到命令时,即可打印,而且在同一时间只能有一个打印任务在执行。我们使用Interlocked方法来实现多线程同步。具体代码如下:
using System.T
namespace MutiThreadSample.ThreadSynchronization
class PrinterWithInterlockTest
/// &summary&
/// 正在使用的打印机
/// 0代表未使用,1代表正在使用
/// &/summary&
public static int UsingPrinter = 0;
/// &summary&
/// 计算机数量
/// &/summary&
public static readonly int ComputerCount = 3;
/// &summary&
/// &/summary&
public static void TestPrint()
Random random = new Random();
for (int i = 0; i & ComputerC i++)
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}",i);
Thread.Sleep(random.Next(3));
thread.Start();
/// &summary&
/// 线程执行操作
/// &/summary&
private static void MyThreadProc()
//使用打印机进行打印
UsePrinter();
//当前线程等待1秒
Thread.Sleep(1000);
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static bool UsePrinter()
//检查大引进是否在使用,如果原始值为0,则为未使用,可以进行打印,否则不能打印,继续等待
if (0 == Interlocked.Exchange(ref UsingPrinter, 1))
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//Code to access a resource that is not thread safe would go here.
//Simulate some work
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
//释放打印机
Interlocked.Exchange(ref UsingPrinter, 0);
return true;
Console.WriteLine("
{0} was denied the lock", Thread.CurrentThread.Name);
return false;
2、lock 关键字
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
public void Function()
System.Object locker= new System.Object();
lock(locker)
// Access thread-sensitive resources.
lock 调用块开始位置的 Enter 和块结束位置的 Exit。
提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例,例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)&暂留&。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。
常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
1)如果实例可以被公共访问,将出现 lock (this) 问题。2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(&myLock&) 问题。最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。关于锁的研究,大家可以参考:
/yank/archive//1321119.html
案例分析:继续使用共享打印机的案例
我们只需对前面的例子稍作修改即可实现lock进行同步。
声明锁对象:
/// &summary&
/// 正在使用的打印机
/// &/summary&
private static object UsingPrinterLocker = new object();
将打印方法修改如下:
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinter()
lock (UsingPrinterLocker)
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
与 lock 关键字类似,监视器防止多个线程同时执行代码块。Enter 方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用 Exit。这与使用 lock 关键字一样。事实上,lock 关键字就是用 Monitor 类来实现的。例如:(继续修改共享打印机案例,增加方法UsePrinterWithMonitor)
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinterWithMonitor()
System.Threading.Monitor.Enter(UsingPrinterLocker);
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
System.Threading.Monitor.Exit(UsingPrinterLocker);
&使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 关键字来实现的,无论是否引发异常它都执行关联的代码块。
4、同步事件和等待句柄
& & & 使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。这需要&同步事件&,它是有两个状态(终止和非终止)的对象,可以用来激活和挂起线程。让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件,则线程将继续执行,而不会延迟。& & &
& & & 同步事件有两种:AutoResetEvent 和 ManualResetEvent。它们之间唯一的不同在于,无论何时,只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
& & &等待句柄,可以通过调用一种等待方法,如 WaitOne、WaitAny 或 WaitAll,让线程等待事件。System.Threading.WaitHandle.WaitOne 使线程一直等待,直到单个事件变为终止状态;System.Threading.WaitHandle.WaitAny 阻止线程,直到一个或多个指示的事件变为终止状态;System.Threading.WaitHandle.WaitAll 阻止线程,直到所有指示的事件都变为终止状态。当调用事件的 Set 方法时,事件将变为终止状态。
& & &AutoResetEvent 允许线程通过发信号互相通信。 通常,当线程需要独占访问资源时使用该类。线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。如果当 AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。 AutoResetEvent 将立即释放线程并返回到非终止状态。可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态:如果初始状态为终止状态,则为 true;否则为 false。AutoResetEvent 也可以同 staticWaitAll 和 WaitAny 方法一起使用。案例:
案例介绍:&
今天我们来做饭,做饭呢,需要一菜、一粥。今天我们吃鱼。
熬粥和做鱼,是比较复杂的工作流程,做粥:选材、淘米、熬制做鱼:洗鱼、切鱼、腌制、烹调为了提高效率,我们用两个线程来准备这顿饭,但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调。
来看实例代码:
using System.T
namespace MutiThreadSample.ThreadSynchronization
/// &summary&
/// 案例:做饭
/// 今天的Dinner准备吃鱼,还要熬粥
/// 熬粥和做鱼,是比较复杂的工作流程,
/// 做粥:选材、淘米、熬制
/// 做鱼:洗鱼、切鱼、腌制、烹调
/// 我们用两个线程来准备这顿饭
/// 但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调
/// &/summary&
class CookResetEvent
/// &summary&
/// &/summary&
private AutoResetEvent resetEvent = new AutoResetEvent(false);
/// &summary&
/// &/summary&
public void Cook()
Thread porridgeThread = new Thread(new ThreadStart(Porridge));
porridgeThread.Name = "Porridge";
porridgeThread.Start();
Thread makeFishThread = new Thread(new ThreadStart(MakeFish));
makeFishThread.Name = "MakeFish";
makeFishThread.Start();
Thread.Sleep(5000);
resetEvent.Reset();
/// &summary&
/// &/summary&
public void Porridge()
Console.WriteLine("Thread:{0},开始选材", Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},开始淘米", Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},开始熬制,需要2秒钟", Thread.CurrentThread.Name);
//需要2秒钟
Thread.Sleep(2000);
Console.WriteLine("Thread:{0},粥已经做好,锅闲了", Thread.CurrentThread.Name);
resetEvent.Set();
/// &summary&
/// &/summary&
public void MakeFish()
Console.WriteLine("Thread:{0},开始洗鱼",Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},开始腌制", Thread.CurrentThread.Name);
//等待锅空闲出来
resetEvent.WaitOne();
Console.WriteLine("Thread:{0},终于有锅了", Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},开始做鱼,需要5秒钟", Thread.CurrentThread.Name);
Thread.Sleep(5000);
Console.WriteLine("Thread:{0},鱼做好了,好香", Thread.CurrentThread.Name);
resetEvent.Set();
&ManualResetEvent与AutoResetEvent用法基本类似,这里不多做介绍。
5、Mutex对象
& & & mutex 与监视器类似;它防止多个线程在某一时间同时执行某个代码块。事实上,名称&mutex&是术语&互相排斥 (mutually exclusive)&的简写形式。然而与监视器不同的是,mutex 可以用来使跨进程的线程同步。mutex 由 Mutex 类表示。当用于进程间同步时,mutex 称为&命名 mutex&,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个 mutex 对象。& & & 尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
& & & 本地 mutex 和系统 mutex& & & Mutex 分两种类型:本地 mutex 和命名系统 mutex。 如果使用接受名称的构造函数创建了 Mutex 对象,那么该对象将与具有该名称的操作系统对象相关联。 命名的系统 mutex 在整个操作系统中都可见,并且可用于同步进程活动。 您可以创建多个 Mutex 对象来表示同一命名系统 mutex,而且您可以使用 OpenExisting 方法打开现有的命名系统 mutex。& & & 本地 mutex 仅存在于进程当中。 进程中引用本地 Mutex 对象的任意线程都可以使用本地 mutex。 每个 Mutex 对象都是一个单独的本地 mutex。
在本地Mutex中,用法与Monitor基本一致
继续修改前面的打印机案例:
声明Mutex对象:
/// &summary&
/// mutex对象
/// &/summary&
private static Mutex mutex = new Mutex();
具体操作:
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinterWithMutex()
mutex.WaitOne();
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
mutex.ReleaseMutex();
多线程调用:
/// &summary&
/// &/summary&
public static void TestPrint()
Random random = new Random();
for (int i = 0; i & ComputerC i++)
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}", i);
Thread.Sleep(random.Next(3));
thread.Start();
/// &summary&
/// 线程执行操作
/// &/summary&
private static void MyThreadProc()
//使用打印机进行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//当前线程等待1秒
Thread.Sleep(1000);
最后的打印机案例代码:
using System.T
namespace MutiThreadSample.ThreadSynchronization
class PrinterWithLockTest
/// &summary&
/// 正在使用的打印机
/// &/summary&
private static object UsingPrinterLocker = new object();
/// &summary&
/// 计算机数量
/// &/summary&
public static readonly int ComputerCount = 3;
/// &summary&
/// mutex对象
/// &/summary&
private static Mutex mutex = new Mutex();
/// &summary&
/// &/summary&
public static void TestPrint()
Random random = new Random();
for (int i = 0; i & ComputerC i++)
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}", i);
Thread.Sleep(random.Next(3));
thread.Start();
/// &summary&
/// 线程执行操作
/// &/summary&
private static void MyThreadProc()
//使用打印机进行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//当前线程等待1秒
Thread.Sleep(1000);
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinter()
lock (UsingPrinterLocker)
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinterWithMonitor()
System.Threading.Monitor.Enter(UsingPrinterLocker);
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
System.Threading.Monitor.Exit(UsingPrinterLocker);
/// &summary&
/// 使用打印机进行打印
/// &/summary&
private static void UsePrinterWithMutex()
mutex.WaitOne();
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
mutex.ReleaseMutex();
&6、读取器/编写器锁
& & & ReaderWriterLockSlim 类允许多个线程同时读取一个资源,但在向该资源写入时要求线程等待以获得独占锁。
& & & 可以在应用程序中使用 ReaderWriterLockSlim,以便在访问一个共享资源的线程之间提供协调同步。 获得的锁是针对 ReaderWriterLockSlim 本身的。& & & 设计您应用程序的结构,让读取和写入操作的时间尽可能最短。 因为写入锁是排他的,所以长时间的写入操作会直接影响吞吐量。 长时间的读取操作会阻止处于等待状态的编写器,并且,如果至少有一个线程在等待写入访问,则请求读取访问的线程也将被阻止。
案例:构造一个线程安全的缓存
using System.T
using System.Collections.G
namespace MutiThreadSample.ThreadSynchronization
/// &summary&
/// 同步Cache
/// &/summary&
public class SynchronizedCache
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary&int, string& innerCache = new Dictionary&int, string&();
/// &summary&
/// &/summary&
/// &param name="key"&&/param&
/// &returns&&/returns&
public string Read(int key)
cacheLock.EnterReadLock();
return innerCache[key];
cacheLock.ExitReadLock();
/// &summary&
/// 添加项
/// &/summary&
/// &param name="key"&&/param&
/// &param name="value"&&/param&
public void Add(int key, string value)
cacheLock.EnterWriteLock();
innerCache.Add(key, value);
cacheLock.ExitWriteLock();
/// &summary&
/// 添加项,有超时限制
/// &/summary&
/// &param name="key"&&/param&
/// &param name="value"&&/param&
/// &param name="timeout"&&/param&
/// &returns&&/returns&
public bool AddWithTimeout(int key, string value, int timeout)
if (cacheLock.TryEnterWriteLock(timeout))
innerCache.Add(key, value);
cacheLock.ExitWriteLock();
return true;
return false;
/// &summary&
/// 添加或者更新
/// &/summary&
/// &param name="key"&&/param&
/// &param name="value"&&/param&
/// &returns&&/returns&
public AddOrUpdateStatus AddOrUpdate(int key, string value)
cacheLock.EnterUpgradeableReadLock();
string result = null;
if (innerCache.TryGetValue(key, out result))
if (result == value)
return AddOrUpdateStatus.U
cacheLock.EnterWriteLock();
innerCache[key] =
cacheLock.ExitWriteLock();
return AddOrUpdateStatus.U
cacheLock.EnterWriteLock();
innerCache.Add(key, value);
cacheLock.ExitWriteLock();
return AddOrUpdateStatus.A
cacheLock.ExitUpgradeableReadLock();
/// &summary&
/// 删除项
/// &/summary&
/// &param name="key"&&/param&
public void Delete(int key)
cacheLock.EnterWriteLock();
innerCache.Remove(key);
cacheLock.ExitWriteLock();
/// &summary&
/// &/summary&
public enum AddOrUpdateStatus
&7、Semaphore 和 SemaphoreSlim
& & & System.Threading.Semaphore 类表示一个命名(系统范围)信号量或本地信号量。 它是一个对 Win32 信号量对象的精简包装。 Win32 信号量是计数信号量,可用于控制对资源池的访问。& & & SemaphoreSlim 类表示一个轻量的快速信号量,可用于在一个预计等待时间会非常短的进程内进行等待。 SemaphoreSlim 会尽可能多地依赖由公共语言运行时 (CLR) 提供的同步基元。 但是,它也会根据需要提供延迟初始化的、基于内核的等待句柄,以支持等待多个信号量。 SemaphoreSlim 还支持使用取消标记,但它不支持命名信号量或使用等待句柄来进行同步。
& & & 线程通过调用 WaitOne 方法来进入信号量,此方法是从 WaitHandle 类派生的。 当调用返回时,信号量的计数将减少。 当一个线程请求项而计数为零时,该线程会被阻止。 当线程通过调用 Release 方法释放信号量时,将允许被阻止的线程进入。 并不保证被阻塞的线程进入信号量的顺序,例如先进先出 (FIFO) 或后进先出 (LIFO)。信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。
案例分析:购买火车票
还得排队进行购买,购买窗口是有限的,只有窗口空闲时才能购买&
using System.T
namespace MutiThreadSample.ThreadSynchronization
/// &summary&
/// 案例:支付流程
/// 如超市、药店、火车票等,都有限定的几个窗口进行结算,只有有窗口空闲,才能进行结算。
/// 我们就用多线程来模拟结算过程
/// &/summary&
class PaymentWithSemaphore
/// &summary&
/// 声明收银员总数为3个,但是当前空闲的个数为0,可能还没开始上班。
/// &/summary&
private static Semaphore IdleCashiers = new Semaphore(0, 3);
/// &summary&
/// 测试支付过程
/// &/summary&
public static void TestPay()
ParameterizedThreadStart start = new ParameterizedThreadStart(Pay);
//假设同时有5个人来买票
for (int i = 0; i & 5; i++)
Thread thread = new Thread(start);
thread.Start(i);
//主线程等待,让所有的的线程都激活
Thread.Sleep(1000);
//释放信号量,2个收银员开始上班了或者有两个空闲出来了
IdleCashiers.Release(2);
/// &summary&
/// &/summary&
/// &param name="obj"&&/param&
public static void Pay(object obj)
Console.WriteLine("Thread {0} begins and waits for the semaphore.", obj);
IdleCashiers.WaitOne();
Console.WriteLine("Thread {0} starts to Pay.",obj);
Thread.Sleep(2000);
Console.WriteLine("Thread {0}: The payment has been finished.",obj);
Console.WriteLine("Thread {0}: Release the semaphore.", obj);
IdleCashiers.Release();
购买火车票
&8、障碍(Barrier)4.0后技术
使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。通过在一系列阶段间移动来协作完成一组任务,此时该组中的每个任务发信号指出它已经到达指定阶段的 Barrier 并且暗中等待其他任务到达。 相同的 Barrier 可用于多个阶段。
9、SpinLock(4.0后) & & &SpinLock结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转。 在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock 的性能将高于其他类型的锁。 不过,我们建议您仅在通过分析确定 System.Threading.Monitor 方法或 Interlocked 方法显著降低了程序的性能时使用 SpinLock。& & & 即使 SpinLock 未获取锁,它也会产生线程的时间片。 它这样做是为了避免线程优先级别反转,并使垃圾回收器能够继续执行。 在使用 SpinLock 时,请确保任何线程持有锁的时间不会超过一个非常短的时间段,并确保任何线程在持有锁时不会阻塞。& & & 由于 SpinLock 是一个值类型,因此,如果您希望两个副本都引用同一个锁,则必须通过引用显式传递该锁。
using System.T
using System.T
using System.Threading.T
namespace MutiThreadSample.ThreadSynchronization
class SpinLockSample
public static void Test()
SpinLock sLock = new SpinLock();
StringBuilder sb = new StringBuilder();
Action action = () =&
bool gotLock = false;
for (int i = 0; i & 100; i++)
gotLock = false;
sLock.Enter(ref gotLock);
sb.Append(i.ToString());
//真正获取之后,才释放
if (gotLock) sLock.Exit();
//多线程调用action
Parallel.Invoke(action, action, action);
Console.WriteLine("输出:{0}",sb.ToString());
10、SpinWait(4.0后)
& & & System.Threading.SpinWait 是一个轻量同步类型,可以在低级别方案中使用它来避免内核事件所需的高开销的上下文切换和内核转换。 在多核计算机上,当预计资源不会保留很长一段时间时,如果让等待线程以用户模式旋转数十或数百个周期,然后重新尝试获取资源,则效率会更高。 如果在旋转后资源变为可用的,则可以节省数千个周期。 如果资源仍然不可用,则只花费了少量周期,并且仍然可以进行基于内核的等待。 这一旋转-等待的组合有时称为&两阶段等待操作&。
下面的基本示例采用微软案例:无锁堆栈
using System.T
namespace MutiThreadSample.ThreadSynchronization
public class LockFreeStack&T&
private volatile Node m_
private class Node { public Node N public T V }
public void Push(T item)
var spin = new SpinWait();
Node node = new Node { Value = item },
while (true)
node.Next =
if (pareExchange(ref m_head, node, head) == head) break;
spin.SpinOnce();
public bool TryPop(out T result)
result = default(T);
var spin = new SpinWait();
while (true)
if (head == null) return false;
if (pareExchange(ref m_head, head.Next, head) == head)
result = head.V
return true;
spin.SpinOnce();
尽管有这么多的技术,但是不同的技术对应不同的场景,我们必须熟悉其特点和适用范围。在应用时,必须具体问题具体分析,选择最佳的同步方式。

我要回帖

更多关于 结束线程 的文章

 

随机推荐