多线程和异步委托基础详解
2015-04-14 16:34 糯米粥 阅读(3567) 评论(0) 编辑 收藏 举报在讲多线程前。先回忆下异步委托:
/*
异步委托自我解释:即用.net委托来自动创建次线程(子线程)以处理异步方法的调用
* 当调用BeginInvoke()方法的时候,程序就会自动创建一个子线程去处理异步委托的方法。
*/
//线程被定义为可执行应用程序中的基本执行单元
1 //1:System.Threading 命名空间包含多种类型,可以使用它们来创建多线程应用程序,Thread类是核心,它代表了某个给定的线程, 2 //若想要得到当前(正在执行某段代码)线程的引用:比如: 3 //得到正在执行这个方法的线程 4 Thread curr = Thread.CurrentThread; 5 //2:在.net平台下,应用程序域和线程之间并不是一 一对应的。事实上,在任何时间,一个应用程序域内都可能有多个线程, 6 //而且,一个特定的线程在它的生命周期内不一定被限定在一个应用程序域中,Windows线程调度程序和CLR会根据需要让线程能够自由地跨越应用程序域的边界。 7 //虽然活动的线程能够跨越多个应用程序域边界,但是在任何一个时间点上,一个线程只能允许在一个应用程序域中,也就是说一个线程同时在多个应用程序域上执行任务是不可能的。 8 9 //获取正在承载当前线程的应用程序域 10 AppDomain ad = Thread.GetDomain(); 11 12 //3:在任何特定的时刻,一个线程也可以移动到一个特定的上下文中。并且它可以有CLR重新部署在一个新的上下文中, 13 //获取当前操作线程的上下文 Context所属命名空间:System.Runtime.Remoting.Contexts 14 Context ctx = Thread.CurrentContext; 15 Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
//开启一个子线程异步调用委托 //IAsyncResult r = a.BeginInvoke(9, 10, null, null); ////模拟等待 : IsCompleted判断异步操作是否完成 //while (!r.IsCompleted) //{ // /* // 可以在此处 做当异步请求没完成之前的逻辑 // */ // Console.WriteLine("异步请求未完成。。。。。"); // Thread.Sleep(1000); //} ////Console.WriteLine(a(1, 2)); //Console.WriteLine("over"); //int c = a.EndInvoke(r); ////以下的代码必须要等Result()执行完成才能执行,所以这些代码可以放到IsCompleted判断中 //Console.WriteLine("代码结束"); //Console.WriteLine(c);
/*
主线程, 调用线程在BeginInvoke()完成之前就被阻塞了
* 即:EndInvoke()后面代码要等BeginInvoke()完成后。才能(主线程)执行,
* 这样效率显然不高。可以用IsCompleted搞定
*/
IsCompleted不是最高效的方式,因为它时时刻刻都会监听异步委托(子线程)是否完成,
/*
* 如果能在异步委托(子线程)完成时,子线程主动通知主线程那是不是更好呢?
* 如果想实现,则在调用BenginInvoke()的时候提供一个System.AsyncCallback委托的实例做完参数,这个参数默认是null,只要提供AsyncCallback对象
* 当异步调用完成时,子线程将自动调用AsyncCallback对象指定的方法
*/
IAsyncCallbac委托的作用
比如可以定义
//当前异步是否完成
private static bool isDone = false;
1 static void AddComplete(IAsyncResult r) 2 { 3 Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 4 Console.WriteLine("Complete....."); 5 6 //using System.Runtime.Remoting.Messaging; 7 AsyncResult result = r as AsyncResult; 8 //AsyncDelegate:获取在其上调用的委托对象 9 Add a = (Add)result.AsyncDelegate; 10 Console.WriteLine("结果是:{0}", a.EndInvoke(r)); 11 12 13 /* 14 获取BeginInvoke传来的额外数据(即异步操作的信息),可以使用IAsyncResult参数的AsyncState属性。 15 * 因为是object类型。所以这里邀请显示强制 转换,所以必须知道实际的类型 16 */ 17 //null值判断 18 if (r.AsyncState != null) 19 { 20 //获取消息对象,并转换成string 21 string msg = r.AsyncState.ToString(); 22 Console.WriteLine("异步委托传来的消息是:{0}", msg); 23 } 24 25 26 isDone = true; 27 }
编写测试代码
IAsyncResult result = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), null); //a.BeginInvoke(9, 9, AddComplete, null);//简写方式 //这里可以做其他事情,等异步委托完成会自动调用 AddComplete 方法 while (!isDone) { Console.WriteLine("异步委还没完成"); Thread.Sleep(3000); }
/*
从是上面可以看出,BeginInvoke()方法最后一个参数一直都是传的null,其实默认值也是null,
* 该参数允许主线程传递额外的状态信息给回调方法,因为这个参数的类型是System.object,所以可以传入
* 任何回调方法,所希望的类型的数据,现在我们可以给AddComplete()传入一个自定义文本消息
* 要在AddComplete()获取数据,可以使用IAsyncResult参数的AsyncState属性。
*/
IAsyncResult result1 = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), "异步委托信息");
system.Threading与线程交互, system.Threading中部分类型
/* * Interlocked:为被多个线程共享访问的类型提供原子操作 * Monitor:使用锁定和特等信息来同步线程对象,C#的lock关键字在后台使用的就是Monitor对象 * Mutex:互斥体,可以用于应用程序域边界之间的同步 * ParamtetrizedThreadStart:委托,它允许线程调用包含任意多个参数的方法 * Semaphore:用于限制对一个资源或一类资源的并发访问的线程数量 * Thread:代表CLR中执行的线程,使用这个类型,能够在初始的应用程序域中创建额外的线程 * ThreadPool:用于和一个进程中的(有CLR维护的)线程池交互 * ThreadPriority:代表了线程调度的优先级别(Highest,Normal等) * ThreadStart:该委托用于定义一个线程所调用的方法,和ParameterizedThreadStart委托不同,这个方法的目标必须符合一种固定的原型 * ThreadState:代表线程处于的状态(Running,Aborted等) * Timer:提供以指定的时间间隔执行方法的机制 * TimerCallback:该委托类型应与Timer类型一起使用 */ //system.Threading.Thread类 /* * system.Threading命名空间中最基本的类型是Thread,它是一个面向对象的包装器,包装特定应用程序域中某个执行单元,Thread类型中定义了 * 许多方法(包括静态的和共享的)。使用这些方法能够在当前应用程序域中创建、挂起、停止、和销毁线程 */ //Thread类主要静态成员 /* * CurrentContext:只读属性,返回当前线程的上下文 * CurrentThread:只读属性,返回当前线程的应用 * GetDomain()和GetDomainID():返回当前应用程序域的应用或当前线程正在运行的域的ID * Sleep():将当前线程 挂起指定的时间 */ //Thread类主要实例级成员 /* * IsAlive:返回Boolean值,指定线程是否开始了 * IsBackground:获取或设置一个值,指示线程是否为后台线程 * Name:给线程指定一个友好的名字 * Priority:获取或设置线程的调度优先级,它是ThreadPriority枚举中的值之一 * ThreadState:获取当前线程的状态,它是ThreadState枚举的值之一 * Abort():通知CLR尽快销毁本线程 * Interrupt():中断当前线程,唤醒处于等待中的线程 * Join():阻塞调用线程,直到某个(调用Join()的)线程终止为止 * Resume():使已挂起的线程继续执行 * Start():通知CLR尽快执行本线程 * Suspend():挂起当前线程,如果线程已经挂起,则不起作用 */
ThreadStart应用,
测试一个线程和多个线程执行方法的效率问题
编写测试类
1 class Printer 2 { 3 public void PrintNumbers() 4 { 5 //显示Thread信息 获取执行该方法的线程 6 Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name); 7 8 //输出数字 9 Console.WriteLine("you numbers"); 10 11 for (int i = 0; i < 10; i++) 12 { 13 Console.Write("{0},", i); 14 Thread.Sleep(2000); 15 } 16 Console.WriteLine(); 17 } 18 }
编写测试代码
1 /* 2 比如主线程执行一个方法A()方法很耗时间, 3 * 那么现在可以在开一个线程(子线程)同时来执行方法A() 4 * 此时:你必须创建一个指向A()方法的ThreadStart委托,接着把这个委托对象传给一个新创建的Thread对象的构造函数 5 * 并且调用这个Thread对象的Start()方法以通知CLR:线程已经准备好执行了。 6 */ 7 8 //是单线程和是多线程 9 Console.WriteLine("请选择多线程或单线程。。。。。"); 10 string threadCount = Console.ReadLine(); 11 12 //命名当前线程 13 Thread t = Thread.CurrentThread; 14 t.Name = "单线程"; 15 16 //线程进程的信息 17 Console.WriteLine("当前线程名称-->{0}", Thread.CurrentThread.Name); 18 19 //创建执行任务的类 20 Printer p = new Printer(); 21 switch (threadCount) 22 { 23 case "2": 24 //设置线程 25 26 /*这里开启一个线程去执行PrintNumbers()方法,所以当前线程(也就是主线程)会接着往下面执行 也就是下面的 "代码段2" 已经执行*/ 27 Thread backgroundThread = new Thread(new ThreadStart(p.PrintNumbers)); 28 backgroundThread.Name = "多线程"; //改变线程名字 29 backgroundThread.Start(); 30 break; 31 case "1": 32 /*由于的单线程,必须等PrintNumbers()方法执行完成 下面的 "代码段2" 才会执行*/ 33 p.PrintNumbers(); 34 break; 35 default: 36 Console.WriteLine("error"); 37 goto case "1"; 38 //break; 39 } 40 //其他工作 代码段 2 41 Console.WriteLine("其他业务逻辑");
多线程结果:
单线程结果
ParameterizedThreadStart委托
/*
* ThreadStart委托仅仅指向无返回值,无参数的方法(上面的PrintNumbers()方法)
* 虽然能满足大多数情况下的要求,但是,如果想把数据传递在给子线程上执行的方法,则需要使用ParameterizedThreadStart委托类型,
* ParameterizedThreadStart能够指向任意带一个System.Object参数的方法
*/
创建一个测试类
class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } }
测试代码
//建立AddParams对象,将其传给子线程 AddParams ap = new AddParams(10, 5); Thread t2 = new Thread(new ParameterizedThreadStart(Addnum)); //Thread t2 = new Thread(Addnum);//简写方式 t2.Start(ap);
AutoResetEvent类
/*
* 一个简单。线程安全的方法是使用AutoResetEvent类,强制线程等待,直到其他线程结束
* 在需要等待的线程中(比如:Main()方法),创建该类的实例,向构造函数传入False,表示尚未收到通知。
* 然后在需要等待的地方调用WaitOne()方法。
*/
编写测试类
定义:AutoResetEvent
private static AutoResetEvent waitHandle = new AutoResetEvent(false);
1 AddParams a2 = new AddParams(9, 5); 2 Thread t3 = new Thread(new ParameterizedThreadStart(Addnum)); 3 t3.Start(a2); 4 5 //等待,直到收到通知,通知其他线程结束,才执行下面的代码 6 waitHandle.WaitOne(); 7 Console.WriteLine("其他线程已经结束");
前台线程和后台线程
//前台线程和后台线程 /* * 前台线程:前台线程能阻止应用程序的终结,一直到所有的前台线程终止后,CLR才能关闭应用程序(即卸载承载的应用程序域) * 后台线程:后台线程(有时候也叫做守护线程)被CLR认为是程序执行中可做出牺牲的途径,即在任何时候(即使这个线程此时正在执行某项工作)都可以被忽略。 * 因此,如果所有的前台线程终止,当应用程序域卸载时,所有的后台线程也会自动终止。(即在所有前台线程终止后,后台线程也跟着终止了) * 但值得注意的是,前台线程和后台线程并不等同于主线程和工作线程,默认情况下,所有通过Thread.Start()方法创建的线程都自动成为前台线程,这意味着,知道所有 * 的线程本身单元的工作都执行完成了。应用程序域才会卸载。大多情况下,这非常必要。可以用后台线程做一些无关紧要的任务:比如每隔几分钟就ping一次邮件服务器看有没有 * 新邮件的应用程序,或更新当前天气条件等 */
并发问题以及lock关键字
/*
* 在构建多应用程序时,需要确保任何共享数据都处于被保护状态,以防止多个线程修改它的值,由于一个应用程序域中
* 的所有线程都能够并发访问共享数据,所以,想象一下当它们正在访问其他中的某个数据项时,会发生什么,由于线程调度器会随机挂起线程,所以如果线程A在完成
* 之前被挂起了。线程B读到的就是一个不稳定的数据。
*/
创建一个测试类
class showNum { public void PrintNum() { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } }
模拟并发问题:创建类showNum 。调用方法PrintNum()打印数字
1 //创建10个Thread对象的数组,并且每一个对象都调用showNum对象的同一个实例 2 showNum num = new showNum(); 3 ////使10个线程全部指向同一个对象的同一方法 4 Thread[] threads = new Thread[10]; 5 for (int i = 0; i < 10; i++) 6 { 7 threads[i] = new Thread(new ThreadStart(num.PrintNum)); 8 //给线程命名 9 threads[i].Name = string.Format("worker threda #{0}", i); 10 } 11 //现在开始每一个线程 12 foreach (Thread t in threads) 13 { 14 t.Start(); 15 } 16 17 /* 18 * 多运行几次会发现。每次运行运行的结果明显不同,在应用程序域中主线程产生了10个工作者进程(子线程) 19 * 每一个工作者线程执行一个PrintNum()方法,由于没有预防锁定共享资源,故在PrintNum()输出到控制台之前,调用PrintNum() 20 * 方法的线程很有可能会被挂起,因为不知道挂起什么时候(或者是否有)可能发生,所以我们得到的是不可预测的结果。 21 * 当每个线程都调用PrintNum()方法的时候,线程调度器可能正在切换线程,这导致了不同的输出结果,故需要找到一种方式来通过 22 * 编程控制对共享的同步访问。即lock关键字进行同步 23 * 24 * 同步访问共享资源的首选技术是C#的lock关键字,这个关键字允许定义一段线程同步的代码语,采用这项技术,后进入的线程不会中断当前线程, 25 * 而是停止自身下一步执行。lock关键字需要定义一个标记(即一个对象引用),线程在进入锁定范围的时候必须获得这个标记。当试图锁定的是一个实例级对象的私有方法时, 26 * 使用方法本身所在对象的引用就可以了。 27 * 如果锁定公共成员中的一段代码。比较安全(也比较推荐)的方式是声明私有的object成员来作为锁标识 28 */
给PrintNum()方法加上lock关键字
定义一个锁标识
private object threadLock = new object();//锁标识
应用
public void PrintNum() { //使用锁标识 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } }
运行后。效果是很明显的
lock关键字同步的两种方式,这里全部归纳在类 showNum 中。
class showNum { //lock关键字同步的两种方式 /* * 这样就有效的设计了一个保证当前线程完成任务的方法,一旦一个线程进入锁定范围,在它退出锁定范围且释放锁定之前, * 其他线程将无法访问锁定标记(本例是当前对象的引用)。因此,如果线程A获得锁定标记,直到它放弃这个锁定标记,其它线程才能够进入锁定范围。 */ //第一种 //如果锁定公共成员中的一段代码。比较安全(也比较推荐)的方式是声明私有的object成员来作为锁标识 private object threadLock = new object();//锁标识 public void PrintNum() { //使用锁标识 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } //第二种 //试图锁定的是一个实例级对象的私有方法,使用本身所在对象的引用就可以了 private void Show() { //使用当前对象作为线程标记 lock (this) { //所以在这范围内的代码是线程安全的 } } //第三种 //试图锁定一个静态方法中的代码,只需要声明一个私有静态对象成员变量作为锁定标记就可以了 private static object staticLock = new object(); public static void showSt() { lock (staticLock) { //所以在这范围内的代码是线程安全的 } } }
扩展知识:System.Threading.Monitor
Monitor类型进行同步跟lock没有实质上的区别
C# lock声明实际上是和System.Threading.Monitor类一同使用时的速记符号。经过编辑器的处理,锁定区域实际上被转化成了如下内容(Reflector查看)
比如上面的PrintNum()方法最后是这样的
* public void PrintNum() { Monitor.Enter(threadLock); try { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } catch (Exception) { Monitor.Exit(threadLock); } } * * 首先请注意:Monitor.Enter()是线程标记的最终容器,而该线程标记作为参数由用户指定给lock关键字,接下来,所有在锁定范围中的代码 * 被try块包含。而对应的finally子句保证了无论出现什么运行错误,线程标记都能被释放(通过Monitor.Exit()方法)。如果程序直接用Monitor类型同步(不用lock关键字) * 看到的结果是一样的。那Monitor类型好处何在?一句话:更好的控制能力。使用Monitor类型,可以使用Monitor。Wait()方法,指示活动的线程等待一段时间,在当前线程完成 * 操作时,使用Monitor.Pulse()或Monitor.PulseAll()通知等待的线程。但大多时候都是用lock就足够了。
使用System.Threading.Interlocked类型进行同步
* 成员:
* CompareExchange()==>安全地比较两个值是否相等,如果相等,用第3个值改变第一个值。
* Decrement()==> 安全递减1
* Exchange()==> 安全地交换数据
* Increment()==>安全递加1
虽然不太起眼,但是原子型地修改单个值在多线程环境下是非常普遍的,假如有个方法名为AddOne(),用它来给名为intVal的整型变量加1,如:
* public void AddOne()
* {
* lock(myLockToken)
* {
* intVal++;
* }
* }
* 可以通过静态的Interlocked.Increment()方法简化代码,以引用方式传入要递增的变量,注意:Increment()方法不但可以修改传入的参数值。还会返回递增后的新值
* public void Addone()
{
int newv = Interlocked.Increment(ref intVal);
}
* 除了使用Increment()和Decrement(),使用Interlocked类型还可以把原子型地赋值给数字对象。例如:想把83赋给一个成员变量,无须明确使用lock关键字或者Monitor逻辑,
* 使用Interlocked.Exchange()即可
* public void Addone()
{
Interlocked.Exchange(ref intVal,83);
}
* 最后,如果想通过线程安全的情况下测试两个值是否相等来改变比较后的指向,可以使用Interlocked.CompareExchange();
* public void Addone()
{
* 如果intVal等于83,把99赋值给intVal
Interlocked.CompareExchange(ref intVal,99,83);
}
使用[Synchronization]特性进行同步
* 最后一个同步化原语是[Synchronization]特性,它位于:System.Runtime.Remoting.Contexts;
* 这个类级别的特性有效地使对象的所有实例的成员都保持线程安全。当CLR分配带[Synchronization]的对象时,它会把这个对象放在同步上下文中。
* 要想对象不被在上下文中移动,就必须让继承ContextBoundObject
比如:
[Synchronization] class AddParams : ContextBoundObject { }
* //showNum全部方法都是线程安全的 * [Synchronization] class showNum:ContextBoundObject { * public void PrintNum() { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } * * 像这样写线程安全的代码是一种"偷懒的"方式。因为它不需要我们实际深入线程控制敏感数据的细节,这种方式的主要问题是:即使一个方法没有使用线程敏感的数据,CLR仍然会锁定 * 该方法的调用,很明显,这样会全面降低性能,所以要小心使用这种方式。
TimerCallback编程
* 许多程序需要定期调用具体的方法,比如:可能有一个应用程序需要在状态栏上通过一个辅助函数显示当前的时间
* 或者,可能希望应用程序调用一个辅助函数,让它经常执行非常紧迫的后台任务,
* 比如:检查是否收到新邮件,像这些情况,就可以使用System.Threading.Timer类型和与其相关的TimerCallback委托
编写测试方法
/* * PrintTime(object state)方法没有返回值,有一个object类型的参数, * 因为TimerCallback仅仅调用符合这样的签名方法。传入TimerCallback委托的参数可以是任何信息,还需要注意的是: * 由于这个参数是System.object类型,所以可以使用Ssystem.Array或者自定义类,结构传入多个值 */ static void PrintTime(object state) { Console.WriteLine("Time is {0}", DateTime.Now.ToLongTimeString()); if (state != null) Console.WriteLine("消息是 {0}", state); }
测试代码
//定义一个TimerCallback委托实例,并把它传入Timer对象中,除了定义TimerCallback委托,Timer的构造函数还允许定义别的信息传送到委托指定的方法中 TimerCallback timeCB = new TimerCallback(PrintTime); Timer tm = new Timer( timeCB, //TimerCallback委托对象 90, //想传入的参数 (null表示没有参数) 0, //在开始之前,等待多长时间执行委托指向的方法(以毫秒为单位) 1000 //每次调用的间隔时间(以毫秒为单位) );
CLR线程池
/*
* 关于线程的核心主题是CLR线程池,当使用委托类型(通过BeginInvoke()方法)进行异步方法调用的时候,CLR并不会创建新的线程,为了取得更高的效率,委托的BeginInvoke()方法
* 创建了由运行时维护的工作者进程池,为了更好地和这些线程进行交互,System.Threading命名空间提供了ThreadPool类类型
* 如果想使用线程池中的工作者线程排队执行一个方法,可以使用ThreadPool.QueueUserWorkItem()方法,这个方法进行了重载,
* 除了可以传递一个WaitCallback委托之外还可以指定一个可选的表示自定义状态数据的System.object。
*/
还记得上面 10 次调用showNum类中PrintNum方法的代码吗?
现在用线程池实现
创建一个跟WaitCallback委托签名的方法 p(),把showNum对象传给p(),在里面调用PrintNum()
//,我们不需要手工创建Thread对象数组,只要把线程池指向 p()方法即可
static void p(object state) { showNum n = state as showNum; n.PrintNum(); }
测试代码
WaitCallback workItem = new WaitCallback(p); showNum num = new showNum(); for (int i = 0; i < 10; i++) { //线程池中的线程总是后台线程 ThreadPool.QueueUserWorkItem(workItem, num); }
/*
* 可以对比一下显示创建Thread对象和使用这个CLR所维护的线程池的好处何在:
*
* 使用线程持的主要好处是:
* 1:线程池减少了线程创建,开始和停止的次数,而这提高了效率。
* 2:使用线程池,能够使我们将注意力放到业务逻辑上而不是多线程架构上。
* 然而,在某些情况下应优先使用手工创建线程,比如:
* 1:如果需要前台线程或设置优先级别。线程池中的线程总是后台线程,且它的优先级是默认的(ThreadPriority.Normal)
* 2:如果需要有一个带有固定标识的线程便于退出,挂起,或通过名字找到它
*/
最后:以下是全部测试源码,可以复制到自己的vs中运行,一个#region是一个知识模块,可以先全部注释,一个一个进行测试即可
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Runtime.Remoting.Contexts; using System.Runtime.Remoting.Messaging; namespace ThreadDemo { class Program { private static AutoResetEvent waitHandle = new AutoResetEvent(false); /* 异步委托自我解释:即用.net委托来自动创建次线程(子线程)以处理异步方法的调用 * 当调用BeginInvoke()方法的时候,程序就会自动创建一个子线程去处理异步委托的方法。 */ public delegate int Add(int x, int y); //当前异步是否完成 private static bool isDone = false; //线程被定义为可执行应用程序中的基本执行单元 static void Main(string[] args) { //1:System.Threading 命名空间包含多种类型,可以使用它们来创建多线程应用程序,Thread类是核心,它代表了某个给定的线程, //若想要得到当前(正在执行某段代码)线程的引用:比如: //得到正在执行这个方法的线程 Thread curr = Thread.CurrentThread; //2:在.net平台下,应用程序域和线程之间并不是一 一对应的。事实上,在任何时间,一个应用程序域内都可能有多个线程, //而且,一个特定的线程在它的生命周期内不一定被限定在一个应用程序域中,Windows线程调度程序和CLR会根据需要让线程能够自由地跨越应用程序域的边界。 //虽然活动的线程能够跨越多个应用程序域边界,但是在任何一个时间点上,一个线程只能允许在一个应用程序域中,也就是说一个线程同时在多个应用程序域上执行任务是不可能的。 //获取正在承载当前线程的应用程序域 AppDomain ad = Thread.GetDomain(); //3:在任何特定的时刻,一个线程也可以移动到一个特定的上下文中。并且它可以有CLR重新部署在一个新的上下文中, //获取当前操作线程的上下文 Context所属命名空间:System.Runtime.Remoting.Contexts Context ctx = Thread.CurrentContext; //Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Add a = new Add(Result); /* 主线程, 调用线程在BeginInvoke()完成之前就被阻塞了 * 即:EndInvoke()后面代码要等BeginInvoke()完成后。才能(主线程)执行, * 这样效率显然不高。可以用IsCompleted搞定 */ #region MyRegion //开启一个子线程异步调用委托 //IAsyncResult r = a.BeginInvoke(9, 10, null, null); ////模拟等待 : IsCompleted判断异步操作是否完成 //while (!r.IsCompleted) //{ // /* // 可以在此处 做当异步请求没完成之前的逻辑 // */ // Console.WriteLine("异步请求未完成。。。。。"); // Thread.Sleep(1000); //} ////Console.WriteLine(a(1, 2)); //Console.WriteLine("over"); //int c = a.EndInvoke(r); ////以下的代码必须要等Result()执行完成才能执行,所以这些代码可以放到IsCompleted判断中 //Console.WriteLine("代码结束"); //Console.WriteLine(c); #endregion #region IAsyncCallbac委托的作用 //IAsyncCallbac委托的作用 /* IsCompleted不是最高效的方式,因为它时时刻刻都会监听异步委托(子线程)是否完成, * 如果能在异步委托(子线程)完成时,子线程主动通知主线程那是不是更好呢? * 如果想实现,则在调用BenginInvoke()的时候提供一个System.AsyncCallback委托的实例做完参数,这个参数默认是null,只要提供AsyncCallback对象 * 当异步调用完成时,子线程将自动调用AsyncCallback对象指定的方法 */ //IAsyncResult result = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), null); //a.BeginInvoke(9, 9, AddComplete, null);//简写方式 //这里可以做其他事情,等异步委托完成会自动调用 AddComplete 方法 //while (!isDone) //{ // Console.WriteLine("异步委还没完成"); // Thread.Sleep(3000); //} #endregion #region 传递和接收自定义数据 //传递和接收自定义数据 /* 从是上面可以看出,BeginInvoke()方法最后一个参数一直都是传的null,其实默认值也是null, * 该参数允许主线程传递额外的状态信息给回调方法,因为这个参数的类型是System.object,所以可以传入 * 任何回调方法,所希望的类型的数据,现在我们可以给AddComplete()传入一个自定义文本消息 * 要在AddComplete()获取数据,可以使用IAsyncResult参数的AsyncState属性。 */ //IAsyncResult result1 = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), "异步委托信息"); #endregion //system.Threading与线程交互, system.Threading中部分类型 /* * Interlocked:为被多个线程共享访问的类型提供原子操作 * Monitor:使用锁定和特等信息来同步线程对象,C#的lock关键字在后台使用的就是Monitor对象 * Mutex:互斥体,可以用于应用程序域边界之间的同步 * ParamtetrizedThreadStart:委托,它允许线程调用包含任意多个参数的方法 * Semaphore:用于限制对一个资源或一类资源的并发访问的线程数量 * Thread:代表CLR中执行的线程,使用这个类型,能够在初始的应用程序域中创建额外的线程 * ThreadPool:用于和一个进程中的(有CLR维护的)线程池交互 * ThreadPriority:代表了线程调度的优先级别(Highest,Normal等) * ThreadStart:该委托用于定义一个线程所调用的方法,和ParameterizedThreadStart委托不同,这个方法的目标必须符合一种固定的原型 * ThreadState:代表线程处于的状态(Running,Aborted等) * Timer:提供以指定的时间间隔执行方法的机制 * TimerCallback:该委托类型应与Timer类型一起使用 */ //system.Threading.Thread类 /* * system.Threading命名空间中最基本的类型是Thread,它是一个面向对象的包装器,包装特定应用程序域中某个执行单元,Thread类型中定义了 * 许多方法(包括静态的和共享的)。使用这些方法能够在当前应用程序域中创建、挂起、停止、和销毁线程 */ //Thread类主要静态成员 /* * CurrentContext:只读属性,返回当前线程的上下文 * CurrentThread:只读属性,返回当前线程的应用 * GetDomain()和GetDomainID():返回当前应用程序域的应用或当前线程正在运行的域的ID * Sleep():将当前线程 挂起指定的时间 */ //Thread类主要实例级成员 /* * IsAlive:返回Boolean值,指定线程是否开始了 * IsBackground:获取或设置一个值,指示线程是否为后台线程 * Name:给线程指定一个友好的名字 * Priority:获取或设置线程的调度优先级,它是ThreadPriority枚举中的值之一 * ThreadState:获取当前线程的状态,它是ThreadState枚举的值之一 * Abort():通知CLR尽快销毁本线程 * Interrupt():中断当前线程,唤醒处于等待中的线程 * Join():阻塞调用线程,直到某个(调用Join()的)线程终止为止 * Resume():使已挂起的线程继续执行 * Start():通知CLR尽快执行本线程 * Suspend():挂起当前线程,如果线程已经挂起,则不起作用 */ #region ThreadStart /* 比如主线程执行一个方法A()方法很耗时间, * 那么现在可以在开一个线程(子线程)同时来执行方法A() * 此时:你必须创建一个指向A()方法的ThreadStart委托,接着把这个委托对象传给一个新创建的Thread对象的构造函数 * 并且调用这个Thread对象的Start()方法以通知CLR:线程已经准备好执行了。 */ //是单线程和是多线程 //Console.WriteLine("请选择多线程或单线程。。。。。"); //string threadCount = Console.ReadLine(); ////命名当前线程 //Thread t = Thread.CurrentThread; //t.Name = "单线程"; ////线程进程的信息 //Console.WriteLine("当前线程名称-->{0}", Thread.CurrentThread.Name); ////创建执行任务的类 //Printer p = new Printer(); //switch (threadCount) //{ // case "2": // //设置线程 // /*这里开启一个线程去执行PrintNumbers()方法,所以当前线程(也就是主线程)会接着往下面执行 也就是下面的 "代码段2" 已经执行*/ // Thread backgroundThread = new Thread(new ThreadStart(p.PrintNumbers)); // backgroundThread.Name = "多线程"; //改变线程名字 // backgroundThread.Start(); // break; // case "1": // /*由于的单线程,必须等PrintNumbers()方法执行完成 下面的 "代码段2" 才会执行*/ // p.PrintNumbers(); // break; // default: // Console.WriteLine("error"); // goto case "1"; // //break; //} ////其他工作 代码段 2 //Console.WriteLine("其他业务逻辑"); #endregion #region ParameterizedThreadStart //ParameterizedThreadStart委托 /* * ThreadStart委托仅仅指向无返回值,无参数的方法(上面的PrintNumbers()方法) * 虽然能满足大多数情况下的要求,但是,如果想把数据传递在给子线程上执行的方法,则需要使用ParameterizedThreadStart委托类型, * ParameterizedThreadStart能够指向任意带一个System.Object参数的方法 */ //建立AddParams对象,将其传给子线程 //AddParams ap = new AddParams(10, 5); //Thread t2 = new Thread(new ParameterizedThreadStart(Addnum)); ////Thread t2 = new Thread(Addnum);//简写方式 //t2.Start(ap); #endregion #region AutoResetEvent //AutoResetEvent类 /* * 一个简单。线程安全的方法是使用AutoResetEvent类,强制线程等待,直到其他线程结束 * 在需要等待的线程中(比如:Main()方法),创建该类的实例,向构造函数传入False,表示尚未收到通知。 * 然后在需要等待的地方调用WaitOne()方法。 */ //AddParams a2 = new AddParams(9, 5); //Thread t3 = new Thread(new ParameterizedThreadStart(Addnum)); //t3.Start(a2); ////等待,直到收到通知,通知其他线程结束,才执行下面的代码 //waitHandle.WaitOne(); //Console.WriteLine("其他线程已经结束"); #endregion #region 前台线程和后台线程 //前台线程和后台线程 /* * 前台线程:前台线程能阻止应用程序的终结,一直到所有的前台线程终止后,CLR才能关闭应用程序(即卸载承载的应用程序域) * 后台线程:后台线程(有时候也叫做守护线程)被CLR认为是程序执行中可做出牺牲的途径,即在任何时候(即使这个线程此时正在执行某项工作)都可以被忽略。 * 因此,如果所有的前台线程终止,当应用程序域卸载时,所有的后台线程也会自动终止。(即在所有前台线程终止后,后台线程也跟着终止了) * 但值得注意的是,前台线程和后台线程并不等同于主线程和工作线程,默认情况下,所有通过Thread.Start()方法创建的线程都自动成为前台线程,这意味着,知道所有 * 的线程本身单元的工作都执行完成了。应用程序域才会卸载。大多情况下,这非常必要。可以用后台线程做一些无关紧要的任务:比如每隔几分钟就ping一次邮件服务器看有没有 * 新邮件的应用程序,或更新当前天气条件等 */ #endregion #region 并发问题以及lock关键字 //并发问题 /* * 在构建多应用程序时,需要确保任何共享数据都处于被保护状态,以防止多个线程修改它的值,由于一个应用程序域中 * 的所有线程都能够并发访问共享数据,所以,想象一下当它们正在访问其他中的某个数据项时,会发生什么,由于线程调度器会随机挂起线程,所以如果线程A在完成 * 之前被挂起了。线程B读到的就是一个不稳定的数据。 */ //模拟并发问题:创建类showNum 。调用方法PrintNum()打印数字 //创建10个Thread对象的数组,并且每一个对象都调用showNum对象的同一个实例 //showNum num = new showNum(); //////使10个线程全部指向同一个对象的同一方法 //Thread[] threads = new Thread[10]; //for (int i = 0; i < 10; i++) //{ // threads[i] = new Thread(new ThreadStart(num.PrintNum)); // //给线程命名 // threads[i].Name = string.Format("worker threda #{0}", i); //} ////现在开始每一个线程 //foreach (Thread t in threads) //{ // t.Start(); //} /* * 多运行几次会发现。每次运行运行的结果明显不同,在应用程序域中主线程产生了10个工作者进程(子线程) * 每一个工作者线程执行一个PrintNum()方法,由于没有预防锁定共享资源,故在PrintNum()输出到控制台之前,调用PrintNum() * 方法的线程很有可能会被挂起,因为不知道挂起什么时候(或者是否有)可能发生,所以我们得到的是不可预测的结果。 * 当每个线程都调用PrintNum()方法的时候,线程调度器可能正在切换线程,这导致了不同的输出结果,故需要找到一种方式来通过 * 编程控制对共享的同步访问。即lock关键字进行同步 * * 同步访问共享资源的首选技术是C#的lock关键字,这个关键字允许定义一段线程同步的代码语,采用这项技术,后进入的线程不会中断当前线程, * 而是停止自身下一步执行。lock关键字需要定义一个标记(即一个对象引用),线程在进入锁定范围的时候必须获得这个标记。当试图锁定的是一个实例级对象的私有方法时, * 使用方法本身所在对象的引用就可以了。 * 如果锁定公共成员中的一段代码。比较安全(也比较推荐)的方式是声明私有的object成员来作为锁标识 */ #endregion #region System.Threading.Monitor //使用System.Threading.Monitor类型进行同步 /* * C# lock声明实际上是和System.Threading.Monitor类一同使用时的速记符号。经过编辑器的处理,锁定区域实际上被转化成了如下内容(Reflector查看) * * * public void PrintNum() { Monitor.Enter(threadLock); try { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } catch (Exception) { Monitor.Exit(threadLock); } } * * 首先请注意:Monitor.Enter()是线程标记的最终容器,而该线程标记作为参数由用户指定给lock关键字,接下来,所有在锁定范围中的代码 * 被try块包含。而对应的finally子句保证了无论出现什么运行错误,线程标记都能被释放(通过Monitor.Exit()方法)。如果程序直接用Monitor类型同步(不用lock关键字) * 看到的结果是一样的。那Monitor类型好处何在?一句话:更好的控制能力。使用Monitor类型,可以使用Monitor。Wait()方法,指示活动的线程等待一段时间,在当前线程完成 * 操作时,使用Monitor.Pulse()或Monitor.PulseAll()通知等待的线程。但大多时候都是用lock就足够了。 * */ #endregion #region Interlocked //使用System.Threading.Interlocked类型进行同步 /* * 成员: * CompareExchange()==>安全地比较两个值是否相等,如果相等,用第3个值改变第一个值。 * Decrement()==> 安全递减1 * Exchange()==> 安全地交换数据 * Increment()==>安全递加1 * * * 虽然不太起眼,但是原子型地修改单个值在多线程环境下是非常普遍的,假如有个方法名为AddOne(),用它来给名为intVal的整型变量加1,如: * public void AddOne() * { * lock(myLockToken) * { * intVal++; * } * } * 可以通过静态的Interlocked.Increment()方法简化代码,以引用方式传入要递增的变量,注意:Increment()方法不但可以修改传入的参数值。还会返回递增后的新值 * public void Addone() { int newv = Interlocked.Increment(ref intVal); } * 除了使用Increment()和Decrement(),使用Interlocked类型还可以把原子型地赋值给数字对象。例如:想把83赋给一个成员变量,无须明确使用lock关键字或者Monitor逻辑, * 使用Interlocked.Exchange()即可 * public void Addone() { Interlocked.Exchange(ref intVal,83); } * 最后,如果想通过线程安全的情况下测试两个值是否相等来改变比较后的指向,可以使用Interlocked.CompareExchange(); * public void Addone() { * 如果intVal等于83,把99赋值给intVal Interlocked.CompareExchange(ref intVal,99,83); } */ //Inter cy = new Inter(); //Thread t5 = new Thread(cy.Addone); //t5.Start(); //for (int i = 0; i < 50; i++) //{ // Thread t6 = new Thread(cy.Addone); // t6.Start(); //} #endregion //使用[Synchronization]特性进行同步 /* * 最后一个同步化原语是[Synchronization]特性,它位于:System.Runtime.Remoting.Contexts; * 这个类级别的特性有效地使对象的所有实例的成员都保持线程安全。当CLR分配带[Synchronization]的对象时,它会把这个对象放在同步上下文中。 * 要想对象不被在上下文中移动,就必须让继承ContextBoundObject * * //showNum全部方法都是线程安全的 * [Synchronization] class showNum:ContextBoundObject { * public void PrintNum() { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } * * 像这样写线程安全的代码是一种"偷懒的"方式。因为它不需要我们实际深入线程控制敏感数据的细节,这种方式的主要问题是:即使一个方法没有使用线程敏感的数据,CLR仍然会锁定 * 该方法的调用,很明显,这样会全面降低性能,所以要小心使用这种方式。 */ #region TimerCallback //TimerCallback编程 /* * 许多程序需要定期调用具体的方法,比如:可能有一个应用程序需要在状态栏上通过一个辅助函数显示当前的时间 * 或者,可能希望应用程序调用一个辅助函数,让它经常执行非常紧迫的后台任务, * 比如:检查是否收到新邮件,像这些情况,就可以使用System.Threading.Timer类型和与其相关的TimerCallback委托 * * */ //定义一个TimerCallback委托实例,并把它传入Timer对象中,除了定义TimerCallback委托,Timer的构造函数还允许定义别的信息传送到委托指定的方法中 //TimerCallback timeCB = new TimerCallback(PrintTime); //Timer tm = new Timer( // timeCB, //TimerCallback委托对象 // 90, //想传入的参数 (null表示没有参数) // 0, //在开始之前,等待多长时间执行委托指向的方法(以毫秒为单位) // 1000 //每次调用的间隔时间(以毫秒为单位) // ); #endregion #region CLR线程池 //CLR线程池 /* * 关于线程的核心主题是CLR线程池,当使用委托类型(通过BeginInvoke()方法)进行异步方法调用的时候,CLR并不会创建新的线程,为了取得更高的效率,委托的BeginInvoke()方法 * 创建了由运行时维护的工作者进程池,为了更好地和这些线程进行交互,System.Threading命名空间提供了ThreadPool类类型 * 如果想使用线程池中的工作者线程排队执行一个方法,可以使用ThreadPool.QueueUserWorkItem()方法,这个方法进行了重载, * 除了可以传递一个WaitCallback委托之外还可以指定一个可选的表示自定义状态数据的System.object。 */ //模拟上面 10 次调用showNum类中PrintNum方法的代码,创建一个跟WaitCallback委托签名的方法 p(),把showNum对象传给p(),在里面调用PrintNum() //,我们不需要手工创建Thread对象数组,只要把线程池指向 p()方法即可 //WaitCallback workItem = new WaitCallback(p); //showNum num = new showNum(); //for (int i = 0; i < 10; i++) //{ // //线程池中的线程总是后台线程 // ThreadPool.QueueUserWorkItem(workItem, num); //} /* * 可以对比一下显示创建Thread对象和使用这个CLR所维护的线程池的好处何在: * * 使用线程持的主要好处是: * 1:线程池减少了线程创建,开始和停止的次数,而这提高了效率。 * 2:使用线程池,能够使我们将注意力放到业务逻辑上而不是多线程架构上。 * 然而,在某些情况下应优先使用手工创建线程,比如: * 1:如果需要前台线程或设置优先级别。线程池中的线程总是后台线程,且它的优先级是默认的(ThreadPriority.Normal) * 2:如果需要有一个带有固定标识的线程便于退出,挂起,或通过名字找到它 */ #endregion //.NET平台下的并行编程 /* * .NET4发布了一个全新的并行编程库。使用System.Threading.Tasks中的类型,可以构建细粒度。可扩展的并行代码。而不必直接与线程和线程池打交道。 * 此外,你还可以使用强类型的LINQ查询来分配工作 * 总体而言,System.Threading.Tasks中的类型(以及System.Threading中的一些相关类型)被称为任务并行库(Task Parallel Library,TPL)。 * TPL使用CLR线程池自动将应用程序的工作动态分配到可用的CPU中,TPL还处理工作分区,线程调度,状态管理和其他低级别的细节操作。最终的结果是。你可以 * 最大限度地提升.Net应用程序的性能,并且避免直接操作线程所带来的复杂性。 * 最后,要知道的是,可以这么做并不意味着应该这么做。编写大量不必要的并行任务会损害.NET程序的性能,同样创建多线程也会使程序的执行变慢。只有在你的任务真正 * 成为程序性能瓶颈的时候才应该使用TPL,如迭代大量的对象,处理多个文件中的数据等。 */ Console.Read(); } static void p(object state) { showNum n = state as showNum; n.PrintNum(); } /* * PrintTime(object state)方法没有返回值,有一个object类型的参数, * 因为TimerCallback仅仅调用符合这样的签名方法。传入TimerCallback委托的参数可以是任何信息,还需要注意的是: * 由于这个参数是System.object类型,所以可以使用Ssystem.Array或者自定义类,结构传入多个值 */ static void PrintTime(object state) { Console.WriteLine("Time is {0}", DateTime.Now.ToLongTimeString()); if (state != null) Console.WriteLine("消息是 {0}", state); } static void AddComplete(IAsyncResult r) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Complete....."); //using System.Runtime.Remoting.Messaging; AsyncResult result = r as AsyncResult; //AsyncDelegate:获取在其上调用的委托对象 Add a = (Add)result.AsyncDelegate; Console.WriteLine("结果是:{0}", a.EndInvoke(r)); /* 获取BeginInvoke传来的额外数据(即异步操作的信息),可以使用IAsyncResult参数的AsyncState属性。 * 因为是object类型。所以这里邀请显示强制 转换,所以必须知道实际的类型 */ //null值判断 if (r.AsyncState != null) { //获取消息对象,并转换成string string msg = r.AsyncState.ToString(); Console.WriteLine("异步委托传来的消息是:{0}", msg); } isDone = true; } static int Result(int x, int y) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(3000); return x + y; } //用AddParams类型作为参数,计算每次相加的结果 static void Addnum(object data) { if (data is AddParams) { Console.WriteLine("当前线程ID是:{0}", Thread.CurrentThread.ManagedThreadId); AddParams ap = (AddParams)data; Console.WriteLine("{0}+{1} is {2}", ap.a, ap.b, ap.a + ap.b); } //通知其他线程,该线程已经结束 waitHandle.Set(); } } class Printer { public void PrintNumbers() { //显示Thread信息 获取执行该方法的线程 Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name); //输出数字 Console.WriteLine("you numbers"); for (int i = 0; i < 10; i++) { Console.Write("{0},", i); Thread.Sleep(2000); } Console.WriteLine(); } } class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } } class showNum { //lock关键字同步的两种方式 /* * 这样就有效的设计了一个保证当前线程完成任务的方法,一旦一个线程进入锁定范围,在它退出锁定范围且释放锁定之前, * 其他线程将无法访问锁定标记(本例是当前对象的引用)。因此,如果线程A获得锁定标记,直到它放弃这个锁定标记,其它线程才能够进入锁定范围。 */ //第一种 //如果锁定公共成员中的一段代码。比较安全(也比较推荐)的方式是声明私有的object成员来作为锁标识 private object threadLock = new object();//锁标识 public void PrintNum() { //Monitor.Enter(threadLock); //try //{ // for (int i = 0; i < 10; i++) // { // //使用线程休眠数秒 // Random r = new Random(); // Thread.Sleep(1000 * r.Next(5)); // Console.Write("{0}, ", i); // } // Console.WriteLine(); //} //catch (Exception) //{ // Monitor.Exit(threadLock); //} //使用锁标识 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用线程休眠数秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } //第二种 //试图锁定的是一个实例级对象的私有方法,使用本身所在对象的引用就可以了 private void Show() { //使用当前对象作为线程标记 lock (this) { //所以在这范围内的代码是线程安全的 } } //第三种 //试图锁定一个静态方法中的代码,只需要声明一个私有静态对象成员变量作为锁定标记就可以了 private static object staticLock = new object(); public static void showSt() { lock (staticLock) { //所以在这范围内的代码是线程安全的 } } } class Inter { private object c = new object(); int num = 0; public void Addone() { //lock (this) //{ // Thread.Sleep(3000); // num++; // Console.WriteLine(num); //} Thread.Sleep(3000); int newv = Interlocked.Increment(ref num); Console.WriteLine(newv); //int newv = Interlocked.Increment(ref num); } } }
并行开发学习资料:http://www.cnblogs.com/huangxincheng/archive/2012/04/02/2429543.html