构建多线程应用程序(Thread、lock锁定范围、Monitor、Interlocked、[Synchronization]、TimerCallback、ThreadPool线程池)

时间:2022-03-08 23:18:36

一、System.Threading.Thread类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections.Specialized;
using System.Reflection;
using System.Data.Linq.Mapping;
using System.Reflection.Emit;
using System.Threading;
namespace ConsoleApplication1
{

    class Program
    {
        static void Main(string[] args)
        {
            Thread t = Thread.CurrentThread;//获得当前正在运行的线程
            t.Name = "ThisPrimaryThread";//设置线程名称

            Console.WriteLine("当前线程正在运行的应用程序域: "+Thread.GetDomain().FriendlyName);
            Console.WriteLine("当前线程正在执行的上下文:" + Thread.CurrentContext.ContextID);

            Console.WriteLine("线程名称: " + t.Name);
            Console.WriteLine("当前线程执行状态: " + t.IsAlive);
            Console.WriteLine("当前线程优先级别: " + t.Priority);
            Console.WriteLine("当前线程的状态: "+t.ThreadState);

            Console.ReadKey();
        }
    }
}


二、创建次线程

//ThreadStart 线程开始执行时,调用无参方法
Thread backgroundThread = new Thread( new ThreadStart(无参方法名) );
backgroundThread.Name = "Sencondary";
backgroundThread.Start(); //通知CLR,尽快执行本线程

 

//ParameterizedThreadStart 线程开始执行时,调用带参方法

Thread backgroundThread2 = new Thread( new ParameterizedThreadStart(带参方法名) );
backgroundThread2.Name = "Sencondary2";
backgroundThread2.Start(ap);

代码示例一:

    class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(Add));
            t.Start();

            Thread t2 = new Thread(new ParameterizedThreadStart(Add2));
            t2.Start("带参方法");

            Console.ReadKey();
        }

        static void Add()
        {
            Console.WriteLine("无参方法");
        }

        static void Add2(object obj)
        {
            Console.WriteLine(obj.ToString());
        }
    }


代码示例二:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("*******次线程******");
            Console.WriteLine("");
            Console.WriteLine("你想用 [1]或[2]线程?请输入1或2");
            string threadCount = Console.ReadLine();

            //命名当前线程
            Thread primaryThread = Thread.CurrentThread;
            primaryThread.Name = "Primary";

            //显示线程信息
            Console.WriteLine("线程:{0} 正在执行主线程。",primaryThread.Name);

            //创建任务类
            Printer p = new Printer();
            AddParams ap = new AddParams(10,20);
            switch (threadCount)
            { 
                case "2":
                    //创建次线程
                    Thread backgroundThread = new Thread(new ThreadStart(p.PrintNumber));//ThreadStart 线程开始执行时,调用无参方法
                    backgroundThread.Name = "Sencondary";
                    backgroundThread.Start();//通知CLR,尽快执行本线程

                    Thread backgroundThread2 = new Thread(new ParameterizedThreadStart(Add));//ParameterizedThreadStart 线程开始执行时,调用带参方法
                    backgroundThread2.Name = "Sencondary2";
                    backgroundThread2.Start(ap);
                    break;
                case "1":
                    p.PrintNumber();
                    Add(ap);
                    break;
                    
                default:
                    goto case "1";
            }

            MessageBox.Show("忙碌中","主线程正在工作中.....");
            Console.ReadLine();

        }

        static void Add(object data)
        {
            if (data is AddParams)
            {
                Console.WriteLine("线程ID:{0},运行Add。",Thread.CurrentThread.ManagedThreadId);
                AddParams ap=(AddParams)data;
                Console.WriteLine(" {0} + {1} = {2}",ap.a,ap.b,ap.a+ap.b);
            }
        }

    }

    public class Printer
    {
        public void PrintNumber()
        {
            Console.WriteLine("线程:{0},正在执行PrintNumber()。",Thread.CurrentThread.Name);
            Console.Write("数字:");
            for (int i = 0; i < 9; i++)
            {
                Console.Write("{0},",i);
                Thread.Sleep(2000);
            }
            Console.WriteLine("");
        }
    }

    class AddParams
    {
        public int a, b;
        public AddParams(int numb1, int numb2)
        {
            a = numb1;
            b = numb2;
        }
    }   
}


 private static AutoResetEvent waintHandle = new AutoResetEvent(false); //强制线程等待

waintHandle.WaitOne();//阻止当前线程

waintHandle.Set();//通知其他线程,该线程已结束

namespace ConsoleApplication1
{
    class Program
    {
        private static AutoResetEvent waintHandle = new AutoResetEvent(false);//强制线程等待
        static void Main(string[] args)
        {
            //创建任务类
            AddParams ap = new AddParams(10,20);

            //创建次线程
            Thread backgroundThread2 = new Thread(new ParameterizedThreadStart(Add));
            backgroundThread2.Name = "Sencondary2";
            backgroundThread2.Start(ap);

            waintHandle.WaitOne();//阻止当前线程

            Console.WriteLine("其他线程在工作!");
            Console.ReadLine();

        }

        static void Add(object data)
        {
            if (data is AddParams)
            {
                Console.WriteLine("线程ID:{0},运行Add。",Thread.CurrentThread.ManagedThreadId);
                AddParams ap=(AddParams)data;
                Console.WriteLine(" {0} + {1} = {2}",ap.a,ap.b,ap.a+ap.b);

                waintHandle.Set();//通知其他线程,该线程已结束
            }
        }

    }

    class AddParams
    {
        public int a, b;
        public AddParams(int numb1, int numb2)
        {
            a = numb1;
            b = numb2;
        }
    }   
}


 

Net的公用语言运行时(CLR)能区分两种不同类型的线程:前台线程(Thread.Start()方法创建的线程,都自动成为前台线程)和后台线程。

这两者的区别就是:

应用程序必须运行完所有的前台线程才可以退出(如设为前台线程,即IsBackground = False, 点窗体的X关闭按钮后程序没有退出,仍在运行。);

而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束(如设为后台线程,即IsBackground = True, 点窗体的X关闭按钮后程序立即退出。)。

 

将前台线程改为后台线程

        static void Main(string[] args)
        {
            //创建任务类
            AddParams ap = new AddParams(10,20);

            //创建次线程
            Thread backgroundThread2 = new Thread(new ParameterizedThreadStart(Add));
            backgroundThread2.IsBackground = true;//将前台线程改为后台线程

            backgroundThread2.Start(ap);

            Console.ReadLine();
        }


 

 三、并发问题

一个应用程序域的所有线程都能够并发访问共享数据。

1、使用c#的lock关键字进行同步

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

     如果:试图锁定静态方法的代码,只需要声明一个私有静态对象成员作为锁定标记。

效果:

构建多线程应用程序(Thread、lock锁定范围、Monitor、Interlocked、[Synchronization]、TimerCallback、ThreadPool线程池)

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Printer ap = new Printer();

            //使用10个线程全部指向同一个对象的同一个方法。
            Thread[] threads=new Thread[10];
            for (int i = 0; i < 10; i++)
            {
                threads[i] = new Thread(new ThreadStart(ap.PrintNumbers));//创建次线程
                threads[i].Name = string.Format("工作线程 #{0}",i);
                
            }
            foreach (Thread t in threads)
                t.Start();
            Console.ReadLine();
        }
    }
    public class Printer
    { 
       //锁标记
        private object threadLock = new object();

        public void PrintNumbers()
        {
            //使用私有对象锁定标记(可以锁定一段代码)
            lock (threadLock)
            {
                Console.WriteLine("->{0} 正在执行 PrintNumbers",Thread.CurrentThread.Name);
                Console.Write("你的数字:");
                for (int i = 0; i < 10; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(1000*r.Next(5));
                    Console.Write("{0},",i);
                }
                Console.WriteLine();
            }
        }
    }

}


使用当前对象作为锁定标记

        public void PrintNumbers()
        {
            //使用当前对象作为锁定标记
            lock (this)
            {
              //所有这个范围的代码是线程安全的
            }
        }


 

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

            //使用Monitor有更好的控制能力,可以使用
            //Monitor.Wait()指示活动线程等待一段时间
            //在当前线程完成后,是用Monitor.Pulse()或Monitor.PulseAll()通知等待中的线程

    public class Printer
    { 
       //锁标记
        private object threadLock = new object();

        public void PrintNumbers()
        {
            //为指定对象上排他锁
            Monitor.Enter(threadLock);
            
            try{
                Console.WriteLine("->{0} 正在执行 PrintNumbers",Thread.CurrentThread.Name);
                Console.Write("你的数字:");
                for (int i = 0; i < 10; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(1000*r.Next(5));
                    Console.Write("{0},",i);
                }
            }finally
            {
                Monitor.Exit(threadLock);//释放排他锁
            }
          }
        }

 

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

System.Threading.Interlocked:原子操作 多线程共享

原子操作:执行一个单独不可分割的指令,在C#中简单的读操作或给一个小于32的字段赋值。

        public void AddOne()
        {
            int intVal = 0;
            int newVal = Interlocked.Increment(ref intVal);//原子操作递增(同: intVal++ )
            Interlocked.Exchange(ref intVal, 83);//原子操作赋值(同: intVal=83 )
            Interlocked.CompareExchange(ref intVal, 99, 83);//(同:如果intVal=83,那么把99赋给intVal)
        }


4、使用[Synchronization]特性进行同步

using System.Runtime.Remoting.Contexts;//上下文

注意要继承于:ContextBoundObject

    //当CLR分配带[Synchronization]的对象时,它会把对象放到同步上下文中。
    [Synchronization]
    public class Printer:ContextBoundObject
    { 
    //...................






     }


四、使用TimerCallback编程

TimerCallback 定期调用的具体方法

效果:

构建多线程应用程序(Thread、lock锁定范围、Monitor、Interlocked、[Synchronization]、TimerCallback、ThreadPool线程池)

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //TimerCallback 定期调用的具体方法
            TimerCallback timeCB = new TimerCallback(PrintTime);
            System.Threading.Timer t = new System.Threading.Timer(
                  timeCB,//TimerCallback委托对象
                  null,//想传人的值(null表示没有参数)
                  0,//在开始之前,等待多长时间(以毫秒为单位)
                1000);//每次调用的间隔时间(以毫秒为单位)

            Console.WriteLine("击键终止");
            Console.ReadLine();
        }

        static void PrintTime(object state)
        {
            Console.WriteLine("时间是:"+DateTime.Now.ToLongTimeString());
        }
    }
}


 

五、CLR 线程池

ThreadPool.QueueUserWorkItem(WaitCallback委托, 可选的自定义状态数据System.Object ); //线程池中执行方法

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApplication1
{
    class Program
    {
        //ThreadPool.QueueUserWorkItem(WaitCallback委托, 可选的自定义状态数据System.Object);//线程池中执行方法
        static void Main(string[] args)
        {
            Printer p = new Printer();
            WaitCallback workItem = new WaitCallback(PrintTheNumber);//线程池要执行的回调方法

            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(workItem, p);//线程池中执行方法
            }

            Console.WriteLine("所有的任务");
            Console.ReadLine();
        }

        static void PrintTheNumber(object state)
        {
            Printer task = (Printer)state;
            task.PrintNumbers();
        }
    }

    

    public class Printer
    {
        public void PrintNumbers()
        {
            lock (this)
            {
                Console.WriteLine("->{0} 正在执行 PrintNumbers", Thread.CurrentThread.Name);
                Console.Write("你的数字:");
                for (int i = 0; i < 10; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(1000 * r.Next(5));
                    Console.Write("{0},", i);
                }
            }
        }
    }
}


 

效果:

构建多线程应用程序(Thread、lock锁定范围、Monitor、Interlocked、[Synchronization]、TimerCallback、ThreadPool线程池)
 

注意:

线程池中的线程是后台线程,没有固定标识线程,不便于退出,挂起或通过名字发现他。

但减少了线程的创建,开始和停止的次数,提高了效率。