zoukankan      html  css  js  c++  java
  • C#中的多线程Thread

    C#多线程Thread

    Thread .net framework1.0提出的。Thread:是C#对计算机资源线程操作的一个封装类

     启动线程的几种方式

    可以通过提供委托来启动线程,该委托表示线程在其类构造函数中执行的方法。 然后调用 Start方法开始执行。

    线程执行的方法无参数

    如果该方法没有参数,则将委托传递给ThreadStart 构造函数。

    ThreadStart的签名:

    public delegate void ThreadStart()  
    private static void ThreadStart1()
            {
                ThreadStart threadStart = () =>
                {
                    Console.WriteLine("ThreadStartMethod begin....");
                };
                Thread thread = new Thread(threadStart);
                thread.Start();//开启一个线程
                Console.WriteLine($"ThreadStart线程ID为:{ thread.ManagedThreadId}");
             }

    线程执行的方法带参数

    如果该方法具有参数,则将委托传递 ParameterizedThreadStart给构造函数。

     它具有签名:

    public delegate void ParameterizedThreadStart(object obj)  
            /// <summary>
            /// 带一个参数【Lambda形式的委托】
            /// </summary>
            private static void ParameterizedThreadStart1()
            {
                //ParameterizedThreadStart是一个带一个形参的委托
                ParameterizedThreadStart method = s =>
                {
                    Console.WriteLine($"ParameterizedThreadStart1传递的参数为{s.ToString()}");
                    Console.WriteLine($"ParameterizedThreadStart1 thread id={Thread.CurrentThread.ManagedThreadId}");
                };
                Thread thread = new Thread(method);
                thread.Start("weiyin1");        
            }
            /// <summary>
            /// 带一个参数[实例化ParameterizedThreadStart委托,委托本质是一个类]
            /// </summary>
            private static void ParameterizedThreadStart2()
            {
                ParameterizedThreadStart method = new ParameterizedThreadStart(DoSomeThing);
                Thread thread = new Thread(method);
                thread.Start("weiyin2");
            }
        private static void DoSomeThing(object methodname)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"{DateTime.Now.ToString()} method: {methodname},ThreadID=[{Thread.CurrentThread.ManagedThreadId}]");
            }
            static void Main(string[] args)
            {
                ThreadStart1();
                ParameterizedThreadStart1();
                ParameterizedThreadStart2();
                Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
            }

    Thead线程等待、回调

     Thread提供了一系列的方法来操作执行的线程,如下所示:

    //thread.Suspend();//暂停线程,微软已弃用
    //thread.Resume();//恢复线程,微软已弃用,无法实时的暂停或者恢复线程
     //thread.Abort();//终结线程 抛出ThreadAbortException异常,以开始终止此线程的过程
     //Thread.ResetAbort();//都会有延时

    同时也提供了线程等待的方法,如下:

               ////如果我们需要线程等待:
                //1.判断状态等待
                //while (thread.ThreadState != ThreadState.Stopped)
                //{
                //    Thread.Sleep(200);
                //}
                ////2.john等待
                //thread.Join();//主线程等待子线程计算完成,卡界面。
                ////可以限时等待
                //thread.Join(2000);//可以限时等待,等待2s,过时不候
                //thread.Priority = ThreadPriority.Highest;
                ////是不是就可以保证是优先执行呢?不能,其只是提供优先执行的概率,优先执行并不代表优先结束,千万不要用这个来控制线程执行的顺序;

    如果我们想要控制线程执行顺序,上面给的thread.Priority = ThreadPriority.Highest;其实不靠谱,那如何实现了?

    上篇博客我们提到了回调【一个动作执行完后,顺序执行另一个动作】,此处一样的道理。

    1.通过while循环判断子线程的执行状态,当其状态为Stopped时(即子线程执行完毕了)后,执行另一个委托绑定的方法【此处为方便,都写的Lambda表达式】。

       此方法虽然可以确保两个委托绑定的方法顺序执行,但是第一个委托执行时,主线程一直在判断其状态,处于卡起的状态,且actionCallBack委托Invoke时线程为主线程。

            static void Main(string[] args)
            {
                Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                ThreadStart threadStart = () => {
                    Console.WriteLine("threadStart委托开始执行");
                    Console.WriteLine($"{DateTime.Now.ToString()} 当前threadStart线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                    Console.WriteLine("threadStart委托执行完毕");
                };
                Action CallBackaction = () => {
                    Console.WriteLine("CallBackaction委托开始执行");
                    Console.WriteLine($"{DateTime.Now.ToString()} 当前CallBackaction线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                    Console.WriteLine("CallBackaction委托执行完毕");
                };
                ThreadWithCallBack(threadStart, CallBackaction);
    private static void ThreadWithCallBack(ThreadStart threadStart, Action actionCallBack)
    {
    Thread thread = new Thread(threadStart); thread.Start(); { //主线程会一直while无限循环判断thread子线程的ThreadState状态(卡界面),等待thread子线程其执行完后,在执行后面的动作。 while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(1000); } actionCallBack.Invoke(); }
    }

    2.采用join进行判断,与上面的类似,其也会卡顿主界面。

        Thread thread = new Thread(threadStart);
                thread.Start();
                thread.Join();//等待thread子线程执行完毕,其会卡界面
                actionCallBack.Invoke();

     3 用一个新的委托将这两个顺序执行的委托包起来,然后启动新的线程执行这个新的委托,实现其与主线程异步,但是内部两个委托执行按顺序同步。

                //把形参的threadStart和actionCallBack又包了一层。。ThreadStart是一个无参无返回值的委托
                //threadStart与actionCallBack的执行是按照先threadStart,后actionCallBack开始的,因为他们包在了
                //threadStart1里面是顺序同步的,同时threadStart1子线程与主线程是异步的。实现子线程内部同步按照指定顺序,主线程外面与子线程异步【即不卡界面】,。
                //多播委托也可
                ThreadStart threadStart1 = new ThreadStart(() =>
                 {
                     threadStart.Invoke();
                     actionCallBack.Invoke();
                 }
                );
                Thread thread = new Thread(threadStart1);
                thread.Start();

    以上都是无返回值的委托,那如果是执行有返回值的委托呢?

    private static void Test()
            {
                Func<int> func = () =>
                {
                    return DateTime.Now.Year;
                };
                //int iresult = ThreadWithReturn(func);//能得到2020吗?
                Func<int> funcreuslt = ThreadWithReturn<int>(func);//不卡界面
                {
                    Console.WriteLine("*******");
                    Console.WriteLine("这里的执行也需要3秒钟");
                }
    
                int iresult = funcreuslt.Invoke();//这里会卡界面
            }
        private static Func<T> ThreadWithReturn<T>(Func<T> func)
            {
                T t = default(T);
                ThreadStart threadStart = new ThreadStart(() =>
                {
                    t = func.Invoke();
                });
                Thread thread = new Thread(threadStart);
                thread.Start();//不卡界面
                Console.WriteLine("子线程ID为" + thread.ManagedThreadId);
                return new Func<T>(
                    () =>
                    {
                        thread.Join();//
                                      //此处这里写就是想必须等到子线程执行完毕后,才返回拿结果;有可能在这里都不用等待,上面的子线程start都执行完了
                        Console.WriteLine("Func线程ID为" + Thread.CurrentThread.ManagedThreadId);
                        return t;
                    });
    
            }

    注意此处ThreadWithReturn方法的返回值里面:thread.join,其是为了确保thread子线程执行完毕,而进行等待。

    结果为:

     因为funcreuslt.Invoke()是在主线程的最后面执行的,所以里面委托绑定的方法也是在前面的打印信息之后执行。

    Thead线程前台、后台线程

    https://www.cnblogs.com/ryanzheng/p/10961777.html 

    1、当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALSE的。

    2、当主线程退出的时候,IsBackground=FALSE的线程还会继续执行下去,直到线程执行结束。

    3、只有IsBackground=TRUE的线程才会随着主线程的退出而退出。

    4、当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。

    5、原理:只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序。

    下面是MSDN给的例子:

    例子中创建了两个线程,foregroundThread【IsBackground=false】和backgroundThread [IsBackground=true],foregroundThread线程执行了RunLoop循环打印10次,Background打印50次,当foreground线程执行完后,主线程也执行完毕后,由于设置的backgroundThread

    为后台线程,其会随着主线程的退出而退出,而不会继续去执行剩下的逻辑(打印其余次数操作)。

    using System;
    using System.Threading;
    
    class Example
    {
        static void Main()
        {
            BackgroundTest shortTest = new BackgroundTest(10);
            Thread foregroundThread = 
                new Thread(new ThreadStart(shortTest.RunLoop));
    
            BackgroundTest longTest = new BackgroundTest(50);
            Thread backgroundThread = 
                new Thread(new ThreadStart(longTest.RunLoop));
            backgroundThread.IsBackground = true;
    
            foregroundThread.Start();
            backgroundThread.Start();
        }
    }
    
    class BackgroundTest
    {
        int maxIterations;
    
        public BackgroundTest(int maxIterations)
        {
            this.maxIterations = maxIterations;
        }
    
        public void RunLoop()
        {
            for (int i = 0; i < maxIterations; i++) {
                Console.WriteLine("{0} count: {1}", 
                    Thread.CurrentThread.IsBackground ? 
                       "Background Thread" : "Foreground Thread", i);
                Thread.Sleep(250);
            }
            Console.WriteLine("{0} finished counting.", 
                              Thread.CurrentThread.IsBackground ? 
                              "Background Thread" : "Foreground Thread");
        }
    }
    // The example displays output like the following:
    //    Foreground Thread count: 0
    //    Background Thread count: 0
    //    Background Thread count: 1
    //    Foreground Thread count: 1
    //    Foreground Thread count: 2
    //    Background Thread count: 2
    //    Foreground Thread count: 3
    //    Background Thread count: 3
    //    Background Thread count: 4
    //    Foreground Thread count: 4
    //    Foreground Thread count: 5
    //    Background Thread count: 5
    //    Foreground Thread count: 6
    //    Background Thread count: 6
    //    Background Thread count: 7
    //    Foreground Thread count: 7
    //    Background Thread count: 8
    //    Foreground Thread count: 8
    //    Foreground Thread count: 9
    //    Background Thread count: 9
    //    Background Thread count: 10
    //    Foreground Thread count: 10
    //    Background Thread count: 11
    //    Foreground Thread finished counting.
    View Code

    TheadPool线程池

    线程池,是在.net framework 2.0提出的,基于Thread做了个升级,Thread功能强大,但是让开发者用不好Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住;

    池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子。(享元模式)其节约资源提升性能;此外,还能管控总数量,防止滥用。

    TheadPool线程池的使用、线程设置

     通过将waitCallback委托对象加入到ThreadPool.QueueUserWorkItem来启动一个线程(线程来自于线程池)

            /// <summary>
            /// 线程池
            /// 在.net framework 2.0基于Thread做了个升级
            /// Thread功能强大,但是让开发者用不好
            /// Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住;
            /// 池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子
            /// 里面获取;用完之后不用销毁,放回池子。(享元模式)
            /// 节约资源提升性能;此外,还能管控总数量,防止滥用。
            /// 
            /// </summary>
            private static void TreadPoolOperate()
            {
                Console.WriteLine("TreadPoolOperate主线程 start");
                Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                //1如何分配一个线程;
                WaitCallback waitCallback = a =>
                {
                    Console.WriteLine($"waitCallback参数为{a}");
                    Console.WriteLine("线程池分配的线程ID为:" + Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine("TreadPoolOperating...");
                };
                //ThreadPool.QueueUserWorkItem(waitCallback);//无参数
                ThreadPool.QueueUserWorkItem(waitCallback, "waitCallback paramInfo");

    可以通过ThreadPool提供的API对线程池中线程数目进行相关操作,如下所示:

    {
                    // 2设置线程池中线程数量
                    //workerThreads:
                    //     线程池中辅助线程的最大数目。
                    //
                    //   completionPortThreads:
                    //     线程池中异步 I/O 线程的最大数目
                    ThreadPool.SetMinThreads(8, 8);
                    ThreadPool.SetMaxThreads(18, 18);//在之前最大线程不能低于计算机的线程数
                                                     //out int minworkTheead 新语法支持引用,相比之前简写了。
                    ThreadPool.GetMinThreads(out int minworkTheead, out int mincompletionPortThreads);
                    Console.WriteLine($"min this  workTheead= {minworkTheead},this completionPortThreads={mincompletionPortThreads}");
                    ThreadPool.GetMaxThreads(out int maxworkTheead, out int maxcompletionPortThreads);
                    Console.WriteLine($"max this  workTheead= {maxworkTheead},this completionPortThreads={maxcompletionPortThreads}");
                    //线程池里的线程可以设置,但是不要随便折腾,因为设置以后,线程相对于当前线程而言,是全局的
                    //Task parallel都是来自于线程池。但是new thread由可以新开一个线程,会占用一个线程池的位置
            }

    ManualResetEvent线程等待

     ManualResetEvent被用于在两个或多个线程间进行线程信号发送。  多个线程可以通过调用ManualResetEvent对象的WaitOne方法进入等待或阻塞状态。当控制线程调用Set()方法,所有等待线程将恢复并继续执行。

    ManualResetEvent对象的初始化

      

        //
        // 摘要:
        //     通知一个或多个正在等待的线程已发生事件。 此类不能被继承。
        [ComVisible(true)]
        public sealed class ManualResetEvent : EventWaitHandle
        {
            //
            // 摘要:
            //     用一个指示是否将初始状态设置为终止的布尔值初始化 System.Threading.ManualResetEvent 类的新实例。
            //
            // 参数:
            //   initialState:
            //     如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。
            public ManualResetEvent(bool initialState);
        }

    其构造函数传入一个bool值,如果bool值为False,则使所有线程阻塞,反之,如果bool值为True,则使所有线程退出阻塞。其默认初始值为False

    ManualResetEvent mre = new ManualResetEvent(false);

    WaitOne方法的使用

      该方法阻塞当前线程并等待其他线程发送信号。如果收到信号,它将返回True,反之返回False。以下演示了如何调用该方法。

    该方法存在于abstract class WaitHandle类中,是一个虚方法。

    API信息如下【其还有很多重载方法,此处不赘述】:

        //
            // 摘要:
            //     阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
            //
            // 返回结果:
            //     如果当前实例收到信号,则为 true。 如果当前实例永不发出信号,则 System.Threading.WaitHandle.WaitOne(System.Int32,System.Boolean)
            //     永不返回。
            //
            // 异常:
            //   T:System.ObjectDisposedException:
            //     已释放当前实例。
            //
            //   T:System.Threading.AbandonedMutexException:
            //     等待结束,因为线程在未释放互斥的情况下退出。 在 Windows 98 或 Windows Millennium Edition 上不会引发此异常。
            //
            //   T:System.InvalidOperationException:
            //     当前实例是另一个应用程序域中的 System.Threading.WaitHandle 的透明代理。
            public virtual bool WaitOne();
    View Code

    Set方法

     该方法用于给所有等待线程发送信号。 Set() 方法的调用使得ManualResetEvent对象的bool变量值为True,所有线程被释放并继续执行。

    Reset方法
       一旦我们调用了ManualResetEvent对象的Set()方法,它的bool值就变为true,我们可以调用Reset()方法来重置该值,Reset()方法重置该值为False。

    此处初始化了一个值为False的ManualResetEvent对象,这意味着所有调用WaitOne的线程将被阻塞【此例主线程调用了WaitOne】,直到有线程调用了 Set() 方法【此处子线程语句最后将事件信号状态变为了True,主线程WaitOne就能继续执行,而不用等待了】。

        private static void TreadPoolOperate()
            {
                Console.WriteLine("TreadPoolOperate主线程 start");
                Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                    //3线程等待:观望式  
                    //ManualResetEvent默认要求参数状态为false--mre.set();打开
                    //ManualResetEvent 参数状态为true->open-->mre.reset();关闭
                    ManualResetEvent mre = new ManualResetEvent(false);//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止
                    ThreadPool.QueueUserWorkItem(a =>
                    {
                        Console.WriteLine(a);
                        Console.WriteLine("当前子线程ID为" + Thread.CurrentThread.ManagedThreadId);
                        Console.WriteLine("线程池里面的线程执行完毕");
                        //将事件状态设置为有信号,从而允许一个或多个等待线程继续执行。
                        mre.Set();//信号由false变为true 
                    });
                    Console.WriteLine("do something else");
                    Console.WriteLine("do something else");
                    Console.WriteLine("do something else");
                    //// 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
                    mre.WaitOne();//判断信号,没有信号(为false),继续等起,有信号了(mre.Set()后,信号为true),不用等待了,可以往后执行了
    //只要mre的状态变为true,则继续往后执行 Console.WriteLine("全部任务执行完成");

     

    而如果我们用值True来对ManualResetEvent对象进行初始化,所有调用WaitOne方法的线程并不会被阻塞,可以进行后续的执行。

    Thead、TheadPool扩展封装

     那如果初始状态设置为无信号,后面又执行WaitOne无限等待,那就会出现死锁的状态。如下例所示:

    #region myregion
                    {
                        ThreadPool.SetMaxThreads(32, 32);//
                        ManualResetEvent mre1 = new ManualResetEvent(false);
                        for (int i = 0; i < 32; i++)
                        {
                            int k = i;
                            ThreadPool.QueueUserWorkItem(t =>
                            {
                                Console.WriteLine($"thread ID={Thread.CurrentThread.ManagedThreadId}");
                            });
                            if (k == 31)
                            {
                                mre1.Set();
                            }
                            else
                            {
                                mre1.WaitOne();//死锁了
                                Console.WriteLine("mre1.WaitOne()");
                            }
                        }
                        mre1.WaitOne();
                        Console.WriteLine("所有任务执行完成");
    
                    }
                    #endregion
     
  • 相关阅读:
    Java操作zip压缩和解压缩文件工具类
    Java操作图片的工具类
    使用Jacob操作Wrod文档的工具类代码
    Java计算文件的SHA码和MD5码
    Java 文件名操作的相关工具类
    Java中windows路径转换成linux路径等工具类
    JDBC的批量批量插入
    显示创建一个表的SQL语句
    MySQL中的保留字
    插入到Mysql数据库中的汉字乱码
  • 原文地址:https://www.cnblogs.com/shuzhongke/p/14190838.html
Copyright © 2011-2022 走看看