System.Threading

 

线程:定义为可执行应用程序中的基本执行单元。

应用程序域:一个应用程序内可能有多个线程。

上下文:一个线程可以移动到一个特定的上下文的实体

导入命名空间:

 

          //得到正在执行这个方法的线程
            Thread currThread = Thread.CurrentThread;

            //获取正在承载当前线程的应用程序
            AppDomain ad = Thread.GetDomain();

            //获取当前操作线程所处的上下文
            System.Runtime.Remoting.Contexts.Context ct = Thread.CurrentContext;

 线程同步的作用:

  同一时间,多个线程都能运行共享的功能块。为了保护资源不被破坏,使用各种原语(比如lock,monitor和[Synchronization]特性或语言关键字支持)来控制线程对它们的访问。

使用System.Threading命名空间中定义的类型,.NET4.0或更高版本的TPL(Task Parallel Library,任务并行库)以及.NET4.5中C#的asyncawait语言关键字,可以比较放心的操作线程。

想要更好的了解线程的方法,先得知道.NET的委托。

 

委托

  .net委托是一个类型安全的、面向对象的函数指针。当定义了一个.NET委托类型时,作为响应,C#编译器将创建一个派生自System.MulticastDelegate的密封类(派生于System.Delegate)  (关于委托的更多介绍以后会写到)

 public delegate int BinaryOp(int x,int y);     //委托跟类同级 
    //它能指向任何一个拥有两个整数参数、返回一个整数的方法

例如: 了解委托我们只用了解原型里面的这几个方法就可以了。

 public delegate int BinaryOp(int x,int y);     //委托跟类同级 
    //它能指向任何一个拥有两个整数参数、返回一个整数的方法

    //public sealed class BinaryOp : MulticastDelegate    //剖析委托,下面主要介绍BeginInvoke和EndInvoke里面的参数
    //{
    //    public BinaryOp(object target, uint functionAddress);             
    //    public int Invoke(int x,int y); //返回两个整数的和
    //    public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state); //开始执行异步委托
    //    public int EndInvoke(IAsyncResult result);   //异步结束返回两个整数的和   调用没有返回值的委托就可以不用这个方法
    //}
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("****请开始你的表演***");
            //输出正在执行中的线程ID
            Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId);
         
            //在同步模式下调用Add()
            BinaryOp b = new BinaryOp(Add);//指向了Add这个方法

            //也能写 b.Invoke(10,10);
            int answer = b(10, 10);      
            //直到Add()方法完了后,在会继续执行下面的
            Console.WriteLine("Doing more work in Main()!");
            Console.WriteLine("10+10 is {0}.",answer);
            Console.WriteLine();

        }
        static int Add(int x, int y) 
        {
            //输出正在执行中的线程ID
            Console.WriteLine("Add() invoked onthread {0}",Thread.CurrentThread.ManagedThreadId);
            //暂定一下,模拟一个耗时的操作
            Thread.Sleep(5000);
            return x + y;
        }
    }

结果:

 

 因为是同步模式下调用了Add方法,由于程序中所有的任务都是被主线程执行,所有显示的ID是相同的值

 

 BeginInvoke()和EndInvoke()

 C#编译器处理delegate关键字的时候,其动态生成的类定义了两个方法BeginInvoke和EndInvoke

   public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state); //用于异步调用的方法
   public int EndInvoke(IAsyncResult result); //用于获取被调用方法的返回值

先来看看BeginInvoke里面的参数。

传入BeginInvoke()的参数的最初集合必须符合C#委托约定(对于BinaryOp,就是两个整型,后两个参数不知道可以给空)。EndInvoke()唯一参数总是IAsyncResult类型。

System.IAsyncResult 接口

查看定义

 // 摘要: 
    //     表示异步操作的状态。
    [ComVisible(true)]
    public interface IAsyncResult
    {
        // 摘要: 
        //     获取用户定义的对象,它限定或包含关于异步操作的信息。
        //
        // 返回结果: 
        //     用户定义的对象,它限定或包含关于异步操作的信息。
        object AsyncState { get; }
        //
        // 摘要: 
        //     获取用于等待异步操作完成的 System.Threading.WaitHandle。
        //
        // 返回结果: 
        //     用于等待异步操作完成的 System.Threading.WaitHandle。
        WaitHandle AsyncWaitHandle { get; }
        //
        // 摘要: 
        //     获取一个值,该值指示异步操作是否同步完成。
        //
        // 返回结果: 
        //     如果异步操作同步完成,则为 true;否则为 false。
        bool CompletedSynchronously { get; }
        //
        // 摘要: 
        //     获取一个值,该值指示异步操作是否已完成。
        //
        // 返回结果: 
        //     如果操作完成则为 true,否则为 false。
        bool IsCompleted { get; }

BeginInvoke()返回的对象实现了IAsyncResult接口,而EndInvoke()需要一个IAsyncResult兼容类型作为它的参数。

所有如果异步调用一个无返回值的方法就不需要EndInvoke方法了。

 

异步调用方法:

还是上面的列子,修改一下

        static void Main(string[] args)
        {
            Console.WriteLine("****请开始你的表演***");
            //输出正在执行中的线程ID
            Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId);
         
            //在次线程中调用add
            BinaryOp b = new BinaryOp(Add);//指向了Add这个方法

            IAsyncResult ifAR = b.BeginInvoke(10, 10, null, null);
            Console.WriteLine("Doing more work in Main()!");
            //执行完后获取Add()方法的结果
            int answer = b.EndInvoke(ifAR);         
            Console.WriteLine("10+10 is {0}.",answer);
            Console.WriteLine();       

        }

我们看到了两个不同的ID值,这说明在这个应用程序域中有两个线程正在运行。

 

同步调用线程

我们一运行,Doing more work inMian()  这个语句就会出现,并没有等到5秒。这样的异步调用中主线程可能会被堵塞,那我们的做异步的优势就不明显了,效率似乎不高,我们需要做另一个同步调用:

 IAsyncResult提供给了IsCompleted属性。使用这个成员,调用线程在调用EndInvoke()之前,便能判断异步调用是否真正完成。

 如果方法没有完成,IsCompleted方法false。

继续改我们的代码:

 static void Main(string[] args)
        {
            Console.WriteLine("****请开始你的表演***");
            //输出正在执行中的线程ID
            Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId);
         
            //在同步模式下调用Add()
            BinaryOp b = new BinaryOp(Add);//指向了Add这个方法

            
            IAsyncResult ifAR = b.BeginInvoke(10, 10, null, null);
//在Add()方法完成之前会一直显示消息
while (!ifAR.IsCompleted) { Console.WriteLine("Doing more work in Main()!"); Thread.Sleep(1000); } // Console.WriteLine("Doing more work in Main()!"); //我们指定Add()方法调用完成了 int answer = b.EndInvoke(ifAR); //直到Add()方法完了后,在会继续执行下面的 Console.WriteLine("10+10 is {0}.",answer); Console.WriteLine(); }

 

 首先我们会执行BeginInvoke方法,会去执行Add()方法,由于需要5秒完成,所以会输出5此doing,我们打断点可以看出先执行Add()在输出doing,直接运行Doing会先出现一遍。

除了IsCompleted属性之外,IAsyncResult接口还提供了AsyncWaiHandle属性以实现更好的灵活的的等待逻辑。返回WaitHandle类型的实例,改实例公开了一个WaitOne()的方法。

此方法可以指定最长等待时间如果超时,返回false.

   while (!ifAR.AsyncWaitHandle.WaitOne(1000,true)) //没有完成异步会一直执行
            {
                Console.WriteLine("Doing more work in Main()!");
            }

这种的形式比上面的好理解些。

 

AsyncCallback 委托的作用  

     不通过一个委托开确定异步调用方法执行是否结束,而是在任务完成时由次线程主动通知调用线程方式。

调用BeginInvoke()时提供一个System.AsyncCallback委托的实例作为参数,默认是Null。只要提供了此对象,异步调用完成,自动调用指定的方法

 

例如:

 
    class Program
    {

        public static bool isDone = false;
        static void Main(string[] args)
        {
            Console.WriteLine("**** AsyncCallbackDelegate 列子 ******");
            Console.WriteLine("Main()  invoke  on thread {0}.",Thread.CurrentThread.ManagedThreadId);

            BinaryOp b = new BinaryOp(Add);
            IAsyncResult iftAR = b.BeginInvoke(10,10,new AsyncCallback(AddComplete),null); 

            while (!isDone)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Working ...");
            }
            Console.ReadLine();
        }
        static int Add(int x, int y) 
        {
            //输出正在执行中的线程ID
            Console.WriteLine("Add() invoked onthread {0}",Thread.CurrentThread.ManagedThreadId);
            //暂定一下,模拟一个耗时的操作
            Thread.Sleep(5000);
            return x + y;
        }
        
//当异步完成后会主动调用此方法
static void AddComplete(IAsyncResult itfAR) { Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId); Console.WriteLine("完成 了"); isDone = true; } }

 

 

 AsyncResult 类的作用

  此类在 System.Remoting.Messaging命名空间下。该类的只读属性 AsyncDelegate 返回了别处创建的原始异步委托引用

  我们向获取在Main()中的BinaryOp委托对象的引用,只需要把由AsyncDelegate 属性返回的System.Object 类型转成BinaryOp类型就可以了

把AddComplete方法加点代码:

 static void AddComplete(IAsyncResult itfAR) 
        {
            Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("完成 了");
            AsyncResult ar = (AsyncResult)itfAR;
            BinaryOp b = (BinaryOp)ar.AsyncDelegate; //获取异步对象
            Console.WriteLine("10+10 is {0}",b.EndInvoke(itfAR));
            isDone = true;
        }

 

 传递和接收自定义状态数据

 异步委托注意最后一个地方就是BeginInvoke()方法的最后一个参数(默认为null)。该参数允许从主线程传递额外的状态信息给回调函数。因为这个参数类型是System.Object,所以可以传递任何回调方法所希望的类型的数据。

例如:

   BinaryOp b = new BinaryOp(Add);
            IAsyncResult iftAR = b.BeginInvoke(10,10,new AsyncCallback(AddComplete),"谢谢你的表演");

为了获取该数据,使用传入IAsyncResult参数的AsyncState属性。 需要转型

  static void AddComplete(IAsyncResult itfAR) 
        {
            Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("完成 了");
            AsyncResult ar = (AsyncResult)itfAR;
            BinaryOp b = (BinaryOp)ar.AsyncDelegate;
            Console.WriteLine("10+10 is {0}",b.EndInvoke(itfAR));
            string msg = (string)itfAR.AsyncState; //获取异步操作信息
            Console.WriteLine(msg);
            isDone = true;
        }

委托就介绍到这里。

 

进入主题:

System.Threading 命名空间       学习一个命名空间,就是学习里面包含类,学习类掌握这个类有什么属性和方法即可

     这个命名空间提供了许多类型用来构建多线程应用程序。 

构造函数:

                                                                               System.Threading命名空间中的部分类型

              Interlocked                                   为被多个线程共享访问的类型提供原子操作

              Monitor                                         使用锁定和等待信号来同步线程对象。C#的lock关键字在后台使用的就是Monitor对象

              Mutex                                           互斥体,可用于应用程序域边界之间的同步

              ParameterizedThreadStart           委托,它允许线程调用包含任意多个参数的方法

              Semaphore                                   用于限制对一个资源或一类资源的并发访问的线程数量

              Thread                                          代表CLR(运行库)中执行的线程。使用这个类型,能够在初始的应用程序域中创建额外的线程

              ThreadPool                                   用于和一个线程中(由CLR维护的)线程池交互

              ThreadPriority                               代表了线程调度的优先级(Highet、Normal)等

              ThreadStart                                  该委托用于定义一个线程调用的方法,和ParameterizedThreadStart 委托不同,这个方法的目标必须符合一种固定的原型

              ThreadState                                 代表线程处于的状态(Running、Aborted等)

              Timer                                            提供以指定的时间间隔执行方法的机制

               TimerCallback                             该委托类型应于Timer类型一起使用

 

更详细的了解去官网

 

System.Threading.Thread 类

       定义:一个面向对象的包装器,包装特定应用程序域中某个执行单元。

                                                                                            主要的静态成员

                     CurrentContext                                           只读属性,返回当前线程的上下文

                     CurrentThread                                            只读属性,返回当前线程的引用

                     GetDomain()和GetDomainID()                   返回当前应用程序域的引用或当前线程正在运行的域的ID

                     Sleep()                                                        将当前线程挂起指定的时间

 

                                                                                            主要的实例级成员

                      IsAlive                                                         返回布尔值,指示线程是否开始了

                      IsBackground                                              获取或设置一个值,指示线程是否为后台线程

                      Name                                                          给线程指定的友好的名字

                      Priority                                                         获取或设置线程的调度优先级。它是ThreadPriority枚举中的值之一

                     ThreadState                                                  获取当前线程的状态。它是ThreadState枚举中的值之一  

                      Abort                                                            通知CLR尽快终止本线程

                      Interrupt                                                       中断当前线程,唤醒处于等待中的线程

                     Join                                                               阻塞调用线程,直到某个(调用Join()的)线程终止为止

                     Resume                                                       使已挂起的线程继续执行

                     Start                                                            通知CLR尽快执行本线程

                     Suspend()                                                   挂起当前线程如果线程已挂起,则改方法不起作用

 

 

获得当前执行线程的统计信息 

static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            Thread primaryThread = Thread.CurrentThread;
            primaryThread.Name = "主线程";
            Console.WriteLine("当前应用域的名字:{0}",Thread.GetDomain().FriendlyName);
            Console.WriteLine("上下文的ID:{0}",Thread.CurrentContext.ContextID);

            Console.WriteLine("线程的名字:{0}",primaryThread.Name);
            Console.WriteLine("线程是否开始:{0}",primaryThread.IsAlive);
            Console.WriteLine("线程的优先级:{0}",primaryThread.Priority);
            Console.WriteLine("当前线程的状态:{0}",primaryThread.ThreadState);
            Console.ReadLine();
        }

 

 

 Name 属性

  不设置将返回空字符串,设置了可以在调试的时候很快找到你想调试的线程。

 

 

 Priority  属性     谨慎使用

   默认情况,所有线程的优先级都是Normal级别。可以在线程的生命周期的任何时候,可以使用ThreadPriority 枚举来修改线程的优先级,且值必须是System,Threading.ThreadPriority枚举中的一个。

  // 摘要: 
    //     指定 System.Threading.Thread 的调度优先级。
    [Serializable]
    [ComVisible(true)]
    public enum ThreadPriority
    {
        // 摘要: 
        //     可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之后。
        Lowest = 0,
        //
        // 摘要: 
        //     可以将 System.Threading.Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
        BelowNormal = 1,
        //
        // 摘要: 
        //     可以将 System.Threading.Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。默认情况下,线程具有
        //     Normal 优先级。
        Normal = 2,
        //
        // 摘要: 
        //     可以将 System.Threading.Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
        AboveNormal = 3,
        //
        // 摘要: 
        //     可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之前。
        Highest = 4,
    }

 

 

构造函数使用:

手动创建此线程

①创建一个方法作为新线程的入口点。

②创建一个 ParameterizedThreadStart(或者ThreadStart)委托,并把上一步所定义方法的地址传给委托的构造函数。

③创建一个Thread对象,并把ParameterizedThreadStart或者ThreadStart 委托作为构造函数的参数

④建立任意初始化线程的特性(名称,优先级)

⑤调用Thread.Start()方法,  Start()方法有重载,一个是无参数代表 ThreadStart 委托 不能传递参数,Start(object o) 可以传递一个Object类型参数,代表ParameterizedThreadStart

 委托,可以传递参数

 

 public delegate void ThreaStarts();     //定义一个委托   写不写都可以,因为有个ThreadStart 委托类

    public class Program
    {
        static void Main(string[] args)
        {            
            Thread th = new Thread(ExecuteInForForeground);//自己写个委托  注意:这个不带参数    简单的写法,不定义委托,直接给方法,自己会匹配        
            //Thread th1 = new Thread(new ThreadStart(ExecuteInForForeground));   //利用命名空间自带的委托
            th.Start();           
            Thread.Sleep(1000);
            Console.WriteLine("Main thread ({0}) exiting...",
                       Thread.CurrentThread.ManagedThreadId);
        }

        private static void ExecuteInForForeground() //配合委托
        {
            DateTime dt = DateTime.Now;
            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); //用来计算运行时间
            Console.WriteLine("Thread {0}:{1},Priority {2}", Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.ThreadState, Thread.CurrentThread.Priority);
            do
            {
                Console.WriteLine("Thread {0}: Elapsed {1:N2}  seconds", Thread.CurrentThread.ManagedThreadId, sw.ElapsedMilliseconds / 1000.0);
                Thread.Sleep(500);  //.05s挂起一次
            } while (sw.ElapsedMilliseconds <= 5000);  //5s
            sw.Stop();
        }
    }

 

 public delegate void ParameterizedThreadStarts(object o);
    public class Program
    {
        static void Main(string[] args)
        {
            Thread th = new Thread(ExecuteInForForeground);//自己写个委托  注意:这个不带参数           
            //Thread th = new Thread(new ParameterizedThreadStart(ExecuteInForForeground));
            th.Start(4000);   //传递参数到我们的委托方法里面
            Thread.Sleep(1000);
            Console.WriteLine("Main thread ({0}) exiting...",
                       Thread.CurrentThread.ManagedThreadId);
        }

        private static void ExecuteInForForeground(object o) //配合委托
        {
            int i;
            try
            {
                i = (int)o;
            }
            catch (InvalidCastException)
            {
                i = 5000;
            }
            DateTime dt = DateTime.Now;
            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
            Console.WriteLine("Thread {0}:{1},Priority {2}", Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.ThreadState, Thread.CurrentThread.Priority);
            do
            {
                Console.WriteLine("Thread {0}: Elapsed {1:N2}  seconds", Thread.CurrentThread.ManagedThreadId, sw.ElapsedMilliseconds / 1000.0);
                Thread.Sleep(500);
            } while (sw.ElapsedMilliseconds <= i);  //5s
            sw.Stop();
        }
    }

 

 

 总结:启用线程我们需要知道去执行哪个方法,这个方法不能有返回值,可以有参可以无参。

 

AutoResetEvent 类

  强制线程等待,直到其他线程结束。

方法:

 向构造函数中传入false,表示尚未收到通知。然后在你需要等待的地方调用WaitOne()方法。你的动作做完了 调用 Set()方法就可以了。

  class Program
    {

        private static AutoResetEvent waitHandle = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            Console.WriteLine("线程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
            Thread th = new Thread(Add);
            AddParams ad = new AddParams(10,10);
            th.Start(ad);

            //等待,直到收到通知   没有收到通知下面的语句不会执行
            waitHandle.WaitOne();
            Console.WriteLine("做完了");
            Console.ReadLine();
        }

        static void Add(object data) 
        {
            if (data is AddParams)
            {
                Console.WriteLine("添加里面的线程ID为:{0}",Thread.CurrentThread.ManagedThreadId);
                AddParams ap = data as AddParams;
                Console.WriteLine("{0}+ {1} = {2}",ap.a,ap.b,ap.a+ap.b);
                //通知其他线程,该线程已结束
                waitHandle.Set();
            }
        }
    }

    class AddParams
    {
        public int a, b;
        public AddParams(int num1,int num2)
        {
            a = num1;
            b = num2;
        }
    }

 

 

前台线程和后台线程

  两者区别:

   只有当所有的前台线程全部执行完毕后,应用程序才能够退出。而对于后台线程,当应用程序退出的时候,后台线程会被强制终止。

 前台线程和后台线程并不等同于主线程和工作者线程。默认情况下,所有通过Thread.Start()方法创建的线程都自动成为前台线程。只要把IsBackground属性设置为True就可以讲线程配置成后台线程。

    static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            Console.WriteLine("线程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
            Printer pr = new Printer();
            Thread th = new Thread(pr.PrintNumbers);       
            th.IsBackground = true;  //后台线程
            th.Start();
            //Console.ReadLine();  没有这句话,一执行就关闭了,数字不会输出在窗口
         
        }
    }

    class Printer 
    {
        public void PrintNumbers() 
        {
            for (int i = 0; i < 10; i++)
            {
                Random r = new Random();
                Thread.Sleep(1000*r.Next(5));
                Console.WriteLine("{0}",i);
            }
           // Console.WriteLine();
        }
    }

 

由上面显示的我们不难看出,我们的程序只会在意前台线程,不会在意后台线程,前台线程一执行完毕,应用程序域就会卸载,不会等后台线程是否执行完成。

所以在工作中线程执行无关紧要的任务时,可以把工作者线程配置成后台线程,让它自己去完成,我们用户可以不用看到。

 

 

并发问题

 在构建多线程应用程序时,需要确保任何共享数据都处于被保护状态,以防止多个线程修改它的值。

 

static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            Console.WriteLine("线程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
            Printer pr = new Printer();
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++)
            {
                //把所有的线程指向同一个方法
                threads[i] = new Thread(new ThreadStart(pr.PrintNumbers));
                threads[i].Name = string.Format("work thread #{0}", i);
            };
            foreach (Thread item in threads)
            {
                item.Start();             
            }
        }
    }

    class Printer 
    {
          /// <summary>
          /// 作为公共方法
          /// </summary>
        public void PrintNumbers() 
        {
            for (int i = 0; i < 10; i++)
            {
                Random r = new Random();
                Thread.Sleep(1000*r.Next(5));
                Console.Write("{0}",i);
            }
            Console.WriteLine();
        }
    }

 

当第一个线程还在访问这个方法的时候,第二个,第三个。。。。也在方法这个方法,这样就会有问题。

 

 

使用C#的lock关键字进行同步

   同步访问共享资源的首选技术是C#的lock关键字。这个关键字允许定义一段线程同步的代码语句。后进入的线程不会中断当前线程,而是停止自身下一步执行。lock关键字需要定义一个标记(即一个对象引用)  ,线程在进入锁定范围的时候必须获得这个标记。当试图锁定的是一个实例级对象的私有方法时,使用方法所在对象的引用就可以了

 class Printer 
    {
        private void SomePrivateMethod() 
        {
            //使用当前对象作为线程标记
            lock (this)
            {
                //所在这个范围内的代码是线程安全的
            }
        }

 如果需要锁定公共成员中的 一段代码,比较安全的方式是声明私有的object成员来作为锁标识

     //锁标识
        private object threadlock = new object();

          /// <summary>
          /// 作为公共方法
          /// </summary>
        public void PrintNumbers() 
        {
//使用私有对象锁定标记
lock (threadlock) { Console.WriteLine("->{0} 在执行方法",Thread.CurrentThread.Name); Console.Write("Your numbers:"); for (int i = 0; i < 10; i++) { Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}", i); } Console.WriteLine(); } }

  一旦一个线程进入范围,在它退出锁定范围且释放锁定之前,其他线程将无法访问锁定标识。

 如果试图锁定静态方法中的代码,只需要声明一个私有静态对象成员变量作为锁定标记就可以了

 

使用 System.Threading.Monitor类型进行同步

  C#lock声明实际上是和System.Threading.Mointor类一同使用时的速记符号。

 

   public void PrintNumbers() 
        {
            Monitor.Enter(threadlock);
            try
            {
                Console.WriteLine("->{0} 在执行方法", Thread.CurrentThread.Name);
                Console.Write("Your numbers:");
                for (int i = 0; i < 10; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(1000 * r.Next(5));
                    Console.Write("{0}", i);
                }
                Console.WriteLine();
            }
            finally 
            {
                Monitor.Exit(threadlock);
            }
           //效果更上面是一样的
           
        }

  lock和Monitor类都可以实现,Monitor可以更好的控制。可以使用它的方法进行处理。

 

使用 System.Threading.Interlocked类型进行同步

      允许我们来原子型操作单个数据,使用它比使用Mointor类型更简单。

   部分静态成员

CompareExchange()                                            安全地比较两个值是否相等。如果相等,将第3个值与其中1个值交换

 Decrement()                                                        安全递减1

Exchange()                                                          安全地交换数据

Increment()                                                         安全地递加1

 

原子型地修改单个值在多线程环境下还是比较普遍。假设有个方法名为AddOne(),用来给变量加1,

   public void AddOne()
        {
            lock (threadlock)
            {
                intVal++;
                Console.WriteLine(intVal);               
            }
        }

我们可以使用Interlocked这个类简化此方法,Increment()方法不但可以修改传入的参数值,还会返回递增后的新值:

  public void AddOne()
        {
            int newVal = Interlocked.Increment(ref intVal);
            Console.WriteLine(newVal);
          //  Console.WriteLine(intVal);

        }

交换数据

   public void AddOne()
        {
          int d=Interlocked.Exchange(ref intVal, 99);
          Console.WriteLine(d);
          Console.WriteLine(intVal);         
        }
  public void AddOne()
        {
            //如果intVal等于83,把99赋给intVal        
            int d = Interlocked.CompareExchange(ref intVal,99,83);
          Console.WriteLine(d);
          Console.WriteLine(intVal);         
        }

返回的都是原始数据也就是intVal的初始值。

 

 

 使用 [ Synchronization]特性进行同步

 同步原语[Synchromization].它位于System.Runtime.Remoting.Contexts命名控件下。这个类级别的特性有效地使对象所有实例的成员都保持线程安全。

  [Synchronization]//它会使对象放在同步上下文中。
    class Printer:ContextBoundObject  //可以让对象在上下文边界中移动  基类
    {

这个类的全部方法都是线程安全的,这是一种偷懒的方式,但这也会全面降低性能。

 

 

使用TimerCallback编程

   许多程序需要定期调用具体的方法。这时就可以使用System.Threading.Timer类型和与其相关的TimerCallback委托

Timer类构造函数

 

例如:每隔一秒输出当前时间,用户按键中断这个程序。

 

   static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            //为Timer类创建委托
            TimerCallback timeCB = new TimerCallback(PrintTime);
            
            //设置Time类
            Timer t = new Timer(timeCB, //TimerCallback委托对象
                null,//想传入的参数
                0, //在开始之前,等待多长时间
                1000 //每次调用的时间间隔
                );
            Console.WriteLine("按键");
            Console.ReadLine();
        }
        //委托方法
        static void PrintTime(object state) 
        {
            Console.WriteLine("Time is: {0}",DateTime.Now.ToLongTimeString());
        }

传递信息给委托指向的方法

   //设置Time类
            Timer t = new Timer(timeCB, //TimerCallback委托对象
                "Hello Word",//想传入的参数
                0, //在开始之前,等待多长时间
                1000 //每次调用的时间间隔
                );

  Console.WriteLine("Time is: {0},Param is {1}",DateTime.Now.ToLongTimeString(),state.ToString());

 

 

CLR线程池  ThreadPool

 当使用委托类型(通过BeginInvoke()方法)进行异步方法调用的时候,CLR并不会创建新的线程。为了取得更高的效率,委托的BeginInvoke()方法创建了有运行时维护的工作者线程池, 为了更好地和这些线程进行交互,System.Threading命名控件提供了ThreadPool类类型。

   如果想使用线程池中的工作者线程排队执行一个方法,可以使用ThreadPool.QueueUserWorkItem()方法。这个方法进行了重载,除了可以传递一个WaitCakkback委托之外海可以指定一个可选的表示自定义状态数据的System,Object.

   static void Main(string[] args)
        {
            Console.WriteLine("请开始你的表演");
            Printer p = new Printer();
            WaitCallback workItem = new WaitCallback(PrintTheNumbers);
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(workItem, p);
            }
            Console.WriteLine("执行完成");
            Console.ReadLine();
           
        }

        static void PrintTheNumbers(object state)
        {
            Printer p = state as Printer;
            p.PrintNumbers();
        }
        /// <summary>
        /// 作为公共方法
        /// </summary>
        public void PrintNumbers()
        {
            Monitor.Enter(threadlock);
            try
            {
                Console.WriteLine("->{0} 在执行方法", Thread.CurrentThread.Name);
                Console.Write("Your numbers:");
                for (int i = 0; i < 10; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(1000 * r.Next(5));
                    Console.Write("{0}", i);
                }
                Console.WriteLine();
            }
            finally
            {
                Monitor.Exit(threadlock);
            }


        }

 

线程池的主要好处:

① 线程池减少了线程创建、开始和停止的次数,而这提高效率。

②使用线程池,能够使我们将注意力放到业务逻辑上而不是多线程架构上。

在某些情况下应线使用手工线程管理,具体如下,

  如果需要前台线程或设置优先级别。线程池中的线程总是后台线程,且它的优先级是默认的(ThreadPriority.Normal)

 如果需要有一个带有固定标识的线程便于退出、挂起或通过名字发现它。

 

 注:这篇只是线程的基础,还有更高级的4.0或更高版本中使用。

 

posted @ 2017-07-27 23:28  Sealee  阅读(1384)  评论(0编辑  收藏  举报