C#多线程总结
多线程的概念
- 操作系统能够优先访问CPU,并能够调整不同程序访问CPU的优先级,避免某一个程序独占CPU的情况发生。
- 可以认为线程是一个虚拟进程,用于独立运行一个特定的程序。
- 线程会消耗大量的操作系统资源,多个线程共享一个物理处理器将导致操作系统忙于管理这些线程,而无法运行程序
- 在单核cpu上并行执行计算任务是没有意义的。
- 在多核cpu上可以使用多线程有效的利用多个cpu的计算能力。这需要组织多个线程间的通信和相互同步。
线程的基本操作
线程的生命周期包括:创建线程 、挂起线程、线程等待、终止线程
创建线程
通过new 一个Thread对象并指定线程执行函数创建线程。调用Start方法开启线程
/// <summary>
/// 线程启动函数
/// </summary>
static void PrintNums()
{
Console.WriteLine("starting ...........");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
Console.WriteLine("end......");
}
/// 创建一个线程
public static void Main()
{
Thread thread = new Thread(PrintNums);
thread.Start();
PrintNums();//主线程调用
}
暂停当前线程
通过在线程函数中调用Thread.Sleep()
暂停当前线程,使线程进入休眠状态。此时线程会占用尽可能少的CPU时间。
//暂停线程
static void PrintNumsWithDelay()
{
Console.WriteLine("starting ...........");
//Log("当前线程状态 :" + Thread.CurrentThread.ThreadState.ToString());
for (int i = 0; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); //每次暂停一秒
Console.WriteLine(i);
}
Console.WriteLine("end......");
}
//创建一个线程并暂停
public static void Test1()
{
Thread t = new Thread(PrintNumsWithDelay);
t.Start();
PrintNums();//主线程调用
}
线程等待
假设有线程t,在主程序中调用了t.Join()方法,该方法允许我们等待直到线程t完成。当线程t完成 时,主程序会继续运行。借助该技术可以实现在两个线程间同步执行步骤。第一个线程会等待另一个线程完成后再继续执行。第一个线程等待时是处于阻塞状态(正如暂停线程中调用 Thread.Sleep方法一样)
/// <summary>
/// 在主线程中等待线程执行玩
/// </summary>
public static void Main()
{
Thread t1 = new Thread(PrintNumsWithDelay);
t1.Start();
t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
Console.WriteLine("Thread t1 finished");
}
终止线程
通过调用Thread.Abort()方法强制终止线程。这会给线程注入ThreadAbortExeception方法,导致线程被终结。这是一个非常危险的操作, 任何时刻发生并可能彻底摧毁应用程序。另外,使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。因此并不推荐使用,Abort方法来关闭线程 。
/// <summary>
/// 终止线程 非常危险,不推荐使用,也不一定能够终止线程
/// </summary>
public static void Main()
{
Thread t = new Thread(PrintNumsWithDelay);
t.Start();
//5s之后终止线程t
Thread.Sleep(5000);
t.Abort();
Console.WriteLine("Thread t has been Abort");
}
获取线程状态
线程状态位于Thread对象的ThreadState属性中。ThreadState属性是一个C#枚举对象。刚开始线程状态为ThreadState.Unstarted,然后我们启动线程,线程状态会从ThreadState.Running变为ThreadState. WaitSleepJoin。 其中:请注意始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。
/// <summary>
/// 线程状态
/// </summary>
public static void Test5()
{
Thread t1 = new Thread(PrintNumsWithDelay);
Log("t1线程状态 :" + t1.ThreadState.ToString());
t1.Start();
Log("t1线程状态 :" + t1.ThreadState.ToString());
t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
Log("t1线程状态 :" + t1.ThreadState.ToString());
Console.WriteLine("Thread t1 finished");
Log("t1线程状态 :" + t1.ThreadState.ToString());
}
线程优先级
通过设置Thread.Priority属性给线程对象设置优先级 ThreadPriority.Highest (最高优先级)、 ThreadPriority.Lowest(最低优先级)。通常优先级更高的线程将获取到更多cpu时间。
/// <summary>
/// 线程优先级
/// </summary>
class ThreadSample
{
private bool isStop = false;
public void Stop()
{
isStop = true;
}
public void CountNums()
{
long counter = 0;
while (!isStop)
{
counter++;
}
Console.WriteLine("{0} with {1,11} priority has a count = {2,13}"
,Thread.CurrentThread.Name,Thread.CurrentThread.Priority,
counter.ToString());
}
}
static void RunThreads()
{
//启动两个线程t1 t2
var sample = new ThreadSample();
Thread t1 = new Thread(sample.CountNums);
t1.Name = "Thread1";
Thread t2 = new Thread(sample.CountNums);
t2.Name = "Thread2";
//设置线程的优先级
t1.Priority = ThreadPriority.Highest; //t1为最高优先级
t2.Priority = ThreadPriority.Lowest; //t2为最低优先级
//启动
t1.Start();
t2.Start();
//主线程阻塞2s
Thread.Sleep(TimeSpan.FromSeconds(2));
sample.Stop(); //停止计数
//等待按键按下
Console.ReadKey();
}
/// <summary>
/// 线程优先级,决定该线程可以占用多少cpu时间
/// </summary>
public static void Test6()
{
Log("当前线程的优先级 priority = " + Thread.CurrentThread.Priority.ToString());
Log("在所有核上运行");
RunThreads();
Thread.Sleep(TimeSpan.FromSeconds(2));
Log("在单个核上运行");
//在该进程下的线程只能在一个核上运行
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
//再次执行
RunThreads();
/*
结果:多核时高优先级的线程通常会比低优先级的线程多执行次数 但是大体接近
单核的时候竞争就更激烈了,用有高优先级的线程会占用更多的cpu时间,而留给低优先级的线程的
cpu时间就更少了。
* 在所有核上运行
Thread1 with Highest priority has a count = 583771892
Thread2 with Lowest priority has a count = 444097830
在单个核上运行
Thread2 with Lowest priority has a count = 32457242
Thread1 with Highest priority has a count = 6534967709
*
*/
}
前台线程和后台线程
- 当主程序启动时定义了两个不同的线程。默认情况下,显式创建的线程是前台线程。通过手动的设置Thread对象的IsBackground属性为ture来创建一个后台线程。
- 前台线程与后台线程的主要区别: 进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
- 如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。
/// <summary>
/// 前台线程和后台线程
/// </summary>
class ThreadSample2
{
private int iterCount = 0;
public ThreadSample2(int count)
{
this.iterCount = count;
}
public void CountNum()
{
for (int i = 0; i <= iterCount; i++)
{
Thread.Sleep(500); //挂起0.5s
//输出次数
Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
}
Log("Thread Finished : " + Thread.CurrentThread.Name);
}
}
public static void Test7()
{
ThreadSample2 samp1 = new ThreadSample2(10);
ThreadSample2 samp2 = new ThreadSample2(20);
//启动两个线程t1,t2,并讲其中一个设置为后台线程 默认是前台线程
Thread t1 = new Thread(samp1.CountNum);
t1.Name = "Foreground";
Thread t2 = new Thread(samp2.CountNum);
t2.Name = "Background";
t2.IsBackground = true; //设置为后台线程
//启动
t1.Start();
t2.Start();
//进程会等所有的前台程序结束完之后才结束;如果只剩下后台程序,则进程会直接结束
}
向线程中传递参数
启动线程的时候需要向线程函数中传递参数,一般有三种方式。
- 将线程函数声明为一个类的成员函数,通过类的成员变量来传递参数。
- 声明一个静态函数当作线程的执行函数,该函数接受一个object类型的参数param,这个参数可以通过Thread.Start(param)传递到线程中。
- 通过lambda表达式的闭包机制传递参数,原理等同于1。C#编辑器会帮我们实现这个类。
/// <summary>
/// 向线程中传递参数
/// </summary>
public static void CountNum(int iterCount)
{
for (int i = 0; i <= iterCount; i++)
{
Thread.Sleep(500); //挂起0.5s
//输出次数
Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
}
Log("Thread Finished : " + Thread.CurrentThread.Name);
}
//方法3:通过object类型的参数传递,参数parma在Start()函数中传递
public static void Count(object param)
{
CountNum((int) param);
}
public static void Test8()
{
Thread t1 = new Thread(Count);
t1.Start(6); //传递参数
//方法3 通过lamda表达式
int num = 8;
Thread t2 = new Thread(()=>{ CountNum(num);});
num = 12;
Thread t3 = new Thread(() => { CountNum(num);});
t2.Start();
t3.Start();
}
线程锁的使用
- 当多个线程同时访问一个资源的时候,容易形成竞争条件,导致错误的产生。为了确保不会发生这种情况,需要保证当前线程1在访问变量A的时候,其他线程必须等待直到线程1完成当前操作。
- 如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这,可能会导致严重的性能问题。
- 可以使用lock关键字来进行加锁操作
public abstract class BaseCounter
{
public abstract void Add();
public abstract void Del();
public abstract long GetRes();
}
/// <summary>
/// 线程不安全的计数器
/// </summary>
public class Counter : BaseCounter
{
private long counter;
public override void Add()
{
counter++;
}
public override void Del()
{
counter--;
}
public override long GetRes()
{
return counter;
}
}
/// <summary>
/// 线程安全的计数器
/// </summary>
public class ThreadSafeCounter : BaseCounter
{
/// <summary>
/// 线程锁对象
/// </summary>
private readonly object lockObj = new object();
private long counter;
public override void Add()
{
lock (lockObj) //锁定一个对象,需要访问该对象的所有其他线程就会处于阻塞状态,并等待直到该对象接触锁定
{
counter++;
}
}
public override void Del()
{
lock (lockObj)
{
counter--;
}
}
public override long GetRes()
{
return counter;
}
}
/// <summary>
/// 线程函数 测试计数器
/// </summary>
/// <param name="c"></param>
static void TestCounter(BaseCounter c)
{
for (int i = 0; i < 10000000; i++)
{
c.Add();
c.Del();
}
}
public static void TestLock()
{
//测试不安全的计数器
//创建3个线程同时对计数器进行加减
Counter c = new Counter();
Thread t1 = new Thread(() => { TestCounter(c);});
Thread t2 = new Thread(() => { TestCounter(c);});
Thread t3 = new Thread(() => { TestCounter(c);});
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
//等三个线程都执行完之后打印结果 看是否为0
Console.WriteLine("count = " + c.GetRes());
//测试线程安全的计数器
//创建3个线程同时对计数器进行加减
ThreadSafeCounter c1 = new ThreadSafeCounter();
t1 = new Thread(() => { TestCounter(c1);});
t2 = new Thread(() => { TestCounter(c1);});
t3 = new Thread(() => { TestCounter(c1);});
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
//等三个线程都执行完之后打印结果 看是否为0
Console.WriteLine("count = " + c1.GetRes());
}
死锁的产生
什么是死锁:
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。
产生死锁的原因
竞争资源
系统中的资源可以分为两类:
- 竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
- 是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
进程间推进顺序非法
1. 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
2. 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
一个死锁的例子
static void LockTooMuch(object lock1, object lock2)
{
lock (lock1) //锁定lock1对象
{
Thread.Sleep(1000); //线程挂起1s
lock (lock2) //试图获取lock2对象的锁定
{
Console.WriteLine("成功获取到lock2对象的锁定");
}
}
}
/// <summary>
/// 造成一个死锁
/// </summary>
public static void Test()
{
//定义两个锁定对象
object lock1 = new object();
object lock2 = new object();
//开启线程
Thread t1 = new Thread(() => {LockTooMuch(lock1,lock2); });
t1.Start();
//在主线程中锁定lock2对象
lock (lock2)
{
Console.WriteLine("这将要产生一个死锁");
Thread.Sleep(1000);
lock (lock1) //试图访问lock1
{
Console.WriteLine("成功获取到lock1对象的锁定");
}
}
}
通过Monitor的超时锁定机制避免死锁
直接使用Monitor类。其拥有TryEnter方法,该方法接受一个超时参数。如果在我们能够获取被lock保护的资源之前,超时参数过期,则该方法会返回 false.
/// <summary>
/// 使用Monitor避免死锁
/// </summary>
public static void Test2()
{
//定义两个锁定对象
object lock1 = new object();
object lock2 = new object();
//开启线程
Thread t1 = new Thread(() => {LockTooMuch(lock1,lock2); });
t1.Start();
//在主线程中使用Monitor类来锁定lock2对象
//在主线程中锁定lock2对象
lock (lock2)
{
Console.WriteLine("使用Monitor.TryEnter避免死锁,有一个超时时间的设定 超时返回false");
Thread.Sleep(1000);
//设置5s的超时时间
if(Monitor.TryEnter(lock1,TimeSpan.FromSeconds(5)))
{
Console.WriteLine("成功获取到lock1对象的锁定");
}
else
{
Console.WriteLine("获取lock1对象失败,");
}
}
}
线程中异常处理
一般来说不要在线程中抛出异常,而是在线程代码中使用try/catch代码块
/// <summary>
/// 在线程中抛出异常,错误的示范
/// </summary>
static void BadFaultThread()
{
Console.WriteLine("开启一个线程");
Thread.Sleep(1000);
throw new Exception("Boom!");
}
/// <summary>
/// 在线程函数中处理异常,正确的示范
/// </summary>
static void FaultThread()
{
try
{
Console.WriteLine("开启一个线程");
Thread.Sleep(1000);
throw new Exception("Boom!");
}
catch (Exception e)
{
Console.WriteLine("捕获异常:" + e.Message);
Console.WriteLine("处理异常");
}
}
public static void Test()
{
Thread t1 = new Thread(FaultThread);
t1.Start();
t1.Join();
Thread.Sleep(1000);
//尝试捕获线程中抛出的异常,失败
try
{
Thread t2 = new Thread(BadFaultThread);
t2.Start();
}
catch (Exception e)
{
//这里不会捕获到发生的异常
Console.WriteLine("捕获异常:" + e.Message);
}
}
线程同步
什么是线程同步
当一个线程执行递增和递减操作时,其他线程需要依次等待。这种常见问题通常被称为线程同步
线程同步的实现方式
- 如果无须共享对象,那么就无须进行线程同步。可以通过重新设计程序来除移共享状态,从而去掉复杂的同步构造。请尽可能避免在多个线程间使用单一对象
- 如果必须使用共享的状态,第二种方式是只使用原子操作。一个操作只占用一个量子的时间,一次就可以完成。所以只有当前操作完成后,其他线程才能执行其他操作
- 内核模式:将等待的线程置于阻塞状态。线程处于阻塞状态时,只会占用尽可能少的CPU时间。这意味着将引入至少一次所谓的上下文切换( context switch),上下文切换是指操作系统的线程调度器。该调度器会保存等待的线程的状态并切换到另一个线程,依次恢复等待的线程的状态。这需要消耗相当多的资源。
- 用户模式:将线程设置成简单的等待,不用切换到阻塞状态。适用于线程只需要等待一小段时间按的情况下。虽然线程等待时会浪费CPU时间,但我们节省了上下文切换耗费的CPU时间。该方式非常轻量,速度很快,但如果线程需要等待较长时间则会浪费大量的CPU时间。
- 混合模式:混合模式先尝试使用用户模式等,待,如果线程等待了足够长的时间,则会切换到阻塞状态以节省CPU资源。
AutoResetEvent
AutoResetEvent是.net线程简易同步方法中的一种。AutoResetEvent 常常被用来在两个线程之间进行信号发送
两个线程共享相同的AutoResetEvent对象,线程1可以通过调用AutoResetEvent对象的WaitOne()方法进入等待状态,然后另外一个线程2通过调用AutoResetEvent对象的Set()方法取消等待的状态。
AutoResetEvent如何工作的
在内存中保持着一个bool值,如果bool值为False,则使线程阻塞,反之,如果bool值为True,则使线程退出阻塞。当我们创建AutoResetEvent对象的实例时,我们在函数构造中传递默认的bool值,以下是实例化AutoResetEvent的例子。
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
WaitOne 方法
该方法阻止当前线程继续执行,并使线程进入等待状态以获取其他线程发送的信号。WaitOne将当前线程置于一个休眠的线程状态。WaitOne方法收到信号后将返回True,否则将返回False。
autoResetEvent.WaitOne();
WaitOne方法的第二个重载版本是等待指定的秒数。如果在指定的秒数后,没有收到任何信号,那么后续代码将继续执行。
static void ThreadMethod()
{
while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2))) //收到信号返回TRUE 否则一直FALSE
{
Console.WriteLine("Continue");
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("Thread got signal");
}
这里我们传递了2秒钟作为WaitOne方法的参数。在While循环中,autoResetEvent对象等待2秒,然后继续执行。当线程取得信号,WaitOne返回为True,然后退出循环,打印"Thread got signal"的消息。
Set 方法
AutoResetEvent Set方法发送信号到等待线程以继续其工作,以下是调用该方法的格式。
autoResetEvent.Set();
AutoResetEvent例子
下面的例子展示了如何使用AutoResetEvent来释放线程。在Main方法中,我们用Task Factory创建了一个线程,它调用了GetDataFromServer方法。调用该方法后,我们调用AutoResetEvent的WaitOne方法将主线程变为等待状态。在调用GetDataFromServer方法时,我们调用了AutoResetEvent对象的Set方法,它释放了主线程,并控制台打印输出dataFromServer方法返回的数据。
public static class TestAutoResetEvent
{
//实例化以一个AutoResetEvent对象 用false初始化
public static AutoResetEvent autoReset = new AutoResetEvent(false);
private static string data;
public static void Test()
{
//启动一个线程
Thread t = new Thread(() => { GetDataFromServer(); });
t.Start();
Console.WriteLine("主线程等待中。。。");
autoReset.WaitOne(); //主线程阻塞 等待接收信号
//接收到信号之后继续执行下面代码
Console.WriteLine("Get A signal = " + data);
}
/// <summary>
/// 模拟从服务器获取数据
/// </summary>
static void GetDataFromServer()
{
Thread.Sleep(3000); //线程阻塞3s
data = "GetDataFinish";
//获取完数据之后发送信号 解除主线程的锁定
autoReset.Set(); //发送信号
}
}
基本的原子操作
指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。原子操作不用阻塞线程就可以避免竞争关系。
使用Interlocked类
Interlocked提供了Increment, Decrement和Add等基本数学操作的原子方法,从而帮助我们,在编写Counter类时无需使用锁。
/// <summary>
/// 线程函数 测试计数器
/// </summary>
/// <param name="c"></param>
static void TestCounter(AtomCounter c)
{
for (int i = 0; i < 10000000; i++)
{
c.Add();
c.Del();
}
}
/// <summary>
/// 使用Interlocked类进行++、--的原子操作
/// </summary>
public class AtomCounter
{
private long counter = 0;
public void Add()
{
Interlocked.Increment(ref counter);
}
public void Del()
{
Interlocked.Decrement(ref counter);
}
public long GetRes()
{
return counter;
}
}
/// <summary>
/// 原子操作
/// </summary>
public static void Test()
{
AtomCounter c1 = new AtomCounter();
var t1 = new Thread(() => { TestCounter(c1);});
var t2 = new Thread(() => { TestCounter(c1);});
var t3 = new Thread(() => { TestCounter(c1);});
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
//等三个线程都执行完之后打印结果 看是否为0
Console.WriteLine("count = " + c1.GetRes());
}
使用SemaphoreSlim信号量
SemaphoreSlim
类为一个轻量、快速的信号量,可在等待时间预计很短的情况下,用于在单个进程内的等待。
该类限制了同时访问同一个资源的线程数量。信号量可用于生产者、消费者线程,其中一个线程始终增加信号量计数,另一个始终减少信号量计数
//创建一个信号量 设置允许的并发线程的初始数量和最大数量
private static int studentCount = 5; //有5个学生
private static int seatNum = 2; //有2个座位
static SemaphoreSlim _sem = new SemaphoreSlim(seatNum,seatNum);
//模拟学生占座到座位上吃饭
static void Eat(string name, int seconds)
{
_sem.Wait(); //排队中等有座位空出来
try
{
Console.WriteLine("{0} 占了一个位置,准备吃饭", name);
Thread.Sleep(TimeSpan.FromSeconds(seconds)); //吃饭时间
}
finally
{
Console.WriteLine("{0} 吃完饭了,让出位置",name);
_sem.Release(); //释放信号量,让出位置
}
}
//信号量
public static void TestSemaphore()
{
//初始化5个线程来模拟学生吃饭,最多同时2个人能占座吃饭
for (int i = 0; i < studentCount; i++)
{
string threadName = "Student " + (i+1);
int secToWait = 2;//等待时间
var t = new Thread(() => Eat(threadName,secToWait));
t.Start();
}
//输出
// Student 4 占了一个位置,准备吃饭
// Student 2 占了一个位置,准备吃饭
// Student 4 吃完饭了,让出位置
// Student 3 占了一个位置,准备吃饭
// Student 2 吃完饭了,让出位置
// Student 5 占了一个位置,准备吃饭
// Student 3 吃完饭了,让出位置
// Student 1 占了一个位置,准备吃饭
// Student 5 吃完饭了,让出位置
// Student 1 吃完饭了,让出位置
}
}
借助信号量限制占座吃饭的并发个数,当两个线程在进行吃饭的时候,其他3个线程需要等待,直到之前的线程完成所做的事情(吃饭)之后并调用semaphore.Release()方法发出信号。
生产者消费者模式
线程池
为什么使用线程池
创建线程的昂贵的操作,为每一个短暂的异步操作创建线程会产生显著的开销。所以需要使用Pooling技术。
线程池可以成功的适用于任何需要大量短暂的开销大的资源的情形。事前分配一定的资源,放到资源池中,每次需要新的资源的时候从池子中获取,而不是新创建一个。当该资源并不需要再被使用的时候,将其回收到池子中。
注意点
- 保持线程中的操作都是非常短暂的。不要再线程池中放入长时间运行的操作,或者阻塞工作线程。
- 请注意线程池中的工作线程都是后台线程。这意味着当所有的前台线程(包括主程序线程)完成后,所有的后台线程将停止工作。
线程池的使用
//异步操作,等待线程池中的线程来调度
static void AsyncOperation(object state)
{
Console.WriteLine("Operation State {0}",state != null ? state :"null" );
Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000); //挂起1s
}
/// <summary>
/// 测试线程池的使用
/// </summary>
public static void TestPool()
{
const int x = 1;
const int y = 1;
const string lambdaState = "lambdaState2";
//将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行
ThreadPool.QueueUserWorkItem(AsyncOperation);
Thread.Sleep(1000);
//放入队列中,等待线程池来执行这个异步操作,并传递一个参数
ThreadPool.QueueUserWorkItem(AsyncOperation, "customState");
//使用lambda表达式
ThreadPool.QueueUserWorkItem(_ =>
{
Console.WriteLine("Operation State {0}",_ != null ? _ :"null" );
Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
//可以利用闭包的特效访问线程外的变量
Console.WriteLine("x+y = {0}",x+y);
Thread.Sleep(1000); //挂起1s
},"customState2");
Thread.Sleep(5000);
Console.WriteLine("主线程退出.");
t x = 1;
const int y = 1;
const string lambdaState = "lambdaState2";
//将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行
ThreadPool.QueueUserWorkItem(AsyncOperation);
Thread.Sleep(1000);
//放入队列中,等待线程池来执行这个异步操作,并传递一个参数
ThreadPool.QueueUserWorkItem(AsyncOperation, "customState");
//使用lambda表达式
ThreadPool.QueueUserWorkItem(_ =>
{
Console.WriteLine("Operation State {0}",_ != null ? _ :"null" );
Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
//可以利用闭包的特效访问线程外的变量
Console.WriteLine("x+y = {0}",x+y);
Thread.Sleep(1000); //挂起1s
},"customState2");
Thread.Sleep(5000);
Console.WriteLine("主线程退出.");