zoukankan      html  css  js  c++  java
  • .NET异步和多线程系列(二)- Thread和ThreadPool

    一、Thread类

    C#里面的多线程:Thread类是C#语言对线程对象的一个封装。

    首先看下如何开启线程,执行委托的内容:

    /// <summary>
    /// 一个比较耗时耗资源的私有方法
    /// </summary>
    private void DoSomethingLong(string name)
    {
        Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
        long lResult = 0;
        for (int i = 0; i < 1_000_000_000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(2000);
        Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
    }
    /// <summary>
    /// 多线程 Thread类是.NET Framework 1.0的时候出现的
    /// Thread:C#对线程对象的一个封装
    /// </summary>
    private void btnThread_Click(object sender, EventArgs e)
    {
        Console.WriteLine($"****************btnThread_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    
        {
            ParameterizedThreadStart method = o => this.DoSomethingLong("btnThread_Click");
            Thread thread = new Thread(method);
            thread.Start("浪子天涯");//开启线程,执行委托的内容
        }
    
        Console.WriteLine($"****************btnThread_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    }

    线程等待、线程优先级、前台线程和后台线程:

    {
        ThreadStart method = () =>
        {
            Thread.Sleep(5000);
            this.DoSomethingLong("btnThread_Click");
            Thread.Sleep(5000);
        };
        Thread thread = new Thread(method);
        thread.Start(); //开启线程,执行委托的内容
    
        //该花括号内的这些方法已经被微软抛弃了,建议不要去用
        {
            //thread.Suspend(); //暂停
            //thread.Resume();//恢复    真的不该要的,暂停不一定马上暂停;让线程操作太复杂了
            //thread.Abort();
            //线程是计算机资源,程序想停下线程,只能向操作系统通知(线程抛异常),
            //会有延时/不一定能真的停下来
            //Thread.ResetAbort();
        }
    
    
        //1等待
        while (thread.ThreadState != ThreadState.Stopped)
        {
            Thread.Sleep(200); //当前线程休息200ms
        }
    
        //2 Join等待
        thread.Join(); //运行这句代码的线程,等待thread的完成
        thread.Join(1000); //最多等待1000ms
    
        Console.WriteLine("这里是线程执行完之后才操作。。。");
    
        //最高优先级:优先执行,但不代表优先完成看,甚至说极端情况下,还有意外发生,不能通过这个来控制线程的执行先后顺序
        thread.Priority = ThreadPriority.Highest;
    
        //是否是后台线程 默认是false
        thread.IsBackground = false; //默认是false 前台线程,进程关闭,线程需要计算完后才退出
        //thread.IsBackground = true;//关闭进程,线程退出
    }

    下面来看下Thread类的使用:

    基于Thread封装一个带有回调的

    /// <summary>
    /// 基于Thread封装一个回调
    /// 回调:启动子线程执行动作A--不阻塞--A执行完后子线程会执行动作B
    /// </summary>
    /// <param name="threadStart">多线程执行的操作</param>
    /// <param name="actionCallback">线程完成后,回调的动作</param>
    private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback)
    {
        //Thread thread = new Thread(threadStart);
        //thread.Start();
        //thread.Join(); //错了,因为方法被阻塞了
        //actionCallback.Invoke();
    
        ThreadStart method = new ThreadStart(() =>
        {
            threadStart.Invoke();
            actionCallback.Invoke();
        });
        new Thread(method).Start();
    }
    {
        ThreadStart threadStart = () => this.DoSomethingLong("btnThread_Click");
        Action actionCallBack = () =>
        {
            Thread.Sleep(2000);
            Console.WriteLine($"This is Calllback {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        };
        this.ThreadWithCallBack(threadStart, actionCallBack);
    }

    基于Thread封装一个带有返回值的

    /// <summary>
    /// 基于Thread封装一个带有返回值的
    /// 1 异步,非阻塞的
    /// 2 还能获取到最终计算结果
    /// 
    /// 既要不阻塞,又要计算结果?不可能!故此处返回一个委托,当外部需要使用结果的时候再阻塞,此时可能已经计算完了。
    /// </summary>
    private 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();
    
        return new Func<T>(() =>
        {
            thread.Join();
            //thread.ThreadState
            return t;
        });
    }
    {
        Func<int> func = () =>
        {
            Thread.Sleep(5000);
            return DateTime.Now.Year;
        };
        Func<int> funcThread = this.ThreadWithReturn(func);//非阻塞
        Console.WriteLine("do something 1");
        Console.WriteLine("do something 2");
        Console.WriteLine("do something 3");
        int iResult = funcThread.Invoke();//阻塞
    }

    控制线程的数量(仅供参考):

    {
        List<Thread> threads = new List<Thread>();
        for (int i = 0; i < 100; i++)
        {
            if (threads.Count(t => t.ThreadState == ThreadState.Running) < 10)
            {
                Thread thread = new Thread(new ThreadStart(() => { }));
                thread.Start();
                threads.Add(thread);
            }
            else
            {
                Thread.Sleep(200);
            }
        }
    }

    二、ThreadPool类

    由于Thread类功能繁多,反而用不好--就像给4岁小孩一把热武器,反而会造成更大的伤害。而且对线程数量也是没有管控的。故微软在.NET Framework 2.0推出来ThreadPool线程池

    如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子。

    保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式)

    节约资源提升性能;此外,还能管控总数量,防止滥用;

    ThreadPool的线程都是后台线程。

    下面我们直接来看下相关代码:

    /// <summary>
    /// ThreadPool线程池
    /// 由于Thread类功能繁多,反而用不好--就像给4岁小孩一把热武器,反而会造成更大的伤害
    /// 对线程数量是没有管控的
    /// 
    /// 线程池是.NET Framework 2.0推出来的
    /// 如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子
    /// 保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式)
    /// 节约资源提升性能;此外,还能管控总数量,防止滥用;
    /// 
    /// ThreadPool的线程都是后台线程
    /// 
    /// 大家课后可以试试,基于ThreadPool去封装回调--返回值的
    /// </summary>
    private void btnThreadPool_Click(object sender, EventArgs e)
    {
        Console.WriteLine($"****************btnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    
        //启动线程
        {
            ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click1"));
            ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click2"), "浪子天涯");
        }
    
        {
            ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
            Console.WriteLine($"当前电脑最大workerThreads={workerThreads} 最大completionPortThreads={completionPortThreads}");
    
            ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);
            Console.WriteLine($"当前电脑最小workerThreads={workerThreadsMin} 最大completionPortThreads={completionPortThreadsMin}");
    
            //设置的线程池数量是进程全局的(慎用,一般不用)
            //委托异步调用--Task--Parrallel--async/await 全部都是线程池的线程
            //直接new Thread不受这个数量限制的(但是会占用线程池的线程数量)
            ThreadPool.SetMaxThreads(8, 8); //设置的最大值,必须大于CPU核数,否则设置无效
            ThreadPool.SetMinThreads(2, 2);
            Console.WriteLine("====================设置线程池数量最大最小====================");
    
            ThreadPool.GetMaxThreads(out int workerThreads1, out int completionPortThreads1);
            Console.WriteLine($"当前电脑最大workerThreads={workerThreads1} 最大completionPortThreads={completionPortThreads1}");
    
            ThreadPool.GetMinThreads(out int workerThreadsMin1, out int completionPortThreadsMin1);
            Console.WriteLine($"当前电脑最大workerThreads={workerThreadsMin1} 最大completionPortThreads={completionPortThreadsMin1}");
        }
    
        //线程等待
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            //false---关闭---Set打开---true---WaitOne就能通过
            //true---打开--ReSet关闭---false--WaitOne就只能等待
            ThreadPool.QueueUserWorkItem(o =>
            {
                this.DoSomethingLong("btnThreadPool_Click1");
                mre.Set();
            });
            Console.WriteLine("Do Something 1");
            Console.WriteLine("Do Something 2");
            Console.WriteLine("Do Something 3");
    
            mre.WaitOne();
            Console.WriteLine("任务已经完成了。。。");
        }
    
        //写多线程的时候有这么一种说法:不要阻塞线程池里面的线程。
        //下面是一个死锁的例子
        {
            ThreadPool.SetMaxThreads(8, 8);
            ManualResetEvent mre = new ManualResetEvent(false);
            for (int i = 0; i < 10; i++)
            {
                int k = i; //此处必须声明一个变量存放i的值,不能直接使用i变量,否则会有问题
                ThreadPool.QueueUserWorkItem(t =>
                {
                    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId.ToString("00")} show {k}");
                    if (k == 9) //设置了最多只允许8个线程,但此处是9,导致死锁了
                    {
                        mre.Set(); //开关打开
                    }
                    else
                    {
                        mre.WaitOne(); //线程等待,阻塞
                    }
                });
            }
    
            if (mre.WaitOne()) //开关没打开,一直等待,死锁了
            {
                Console.WriteLine("任务全部执行成功!");
            }
        }
    
        Console.WriteLine($"****************btnThreadPool_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
            $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    }

    Demo源码:

    链接:https://pan.baidu.com/s/1wVscaka37emNGz9x-rm0qA 
    提取码:3l2e

    此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13550714.html

    版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

  • 相关阅读:
    css clear
    设置 系统软件
    Canvas 生成 bitmap
    HashTable
    JSF
    android 自定义 锁屏
    java socket
    php mysql 配置
    mysql 启动方法
    jQuery 表格
  • 原文地址:https://www.cnblogs.com/xyh9039/p/13550714.html
Copyright © 2011-2022 走看看