• 20181104_C#线程之Thread_ThreadPool_使用Thread实现回到和带参数的回调


    C#   .net  Framework多线程演变路径:

    1.0    1.1 时代使用Thread

    2.0    时代使用ThreadPool

    3.0    时代使用Task

    4.0    时代使用Parallel

    4.5 时代使用 async/awit

    一.   DoSomethingLong方法如下:

    /// <summary>
            /// 一个比较耗时耗资源的私有方法
            /// </summary>
            /// <param name="name"></param>
            private void DoSomethingLong(string name)
            {
                Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                long lResult = 0;
                for (int i = 0; i < 1000000000; i++)
                {
                    lResult += i;
                }
                //Thread.Sleep(2000);
    
                Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
            }
    

    二.   使用Thread

    a)         下面代码演示, 如何使用Thread来启动一个线程

     //public Thread(ThreadStart start, int maxStackSize);  //maxStackSize 表示指定这个线程最大可以使用多少内存空间, 一般不用设置
                ThreadStart threadStart = () => this.DoSomethingLong("btnThreads_Click");
                Thread thread = new Thread(threadStart);
    thread.Start();
    

     b) Thread的一些其它api

    i.	thread.Suspend();//(弃用)线程挂起, 使线程暂停执行; 已过期, 不推荐使用, 会导致死锁, 因为线程的执行的时候, 是会占用资源的, 虽然手动让线程暂停执行了, 但是它占用的资源是不会释放的
    ii.	thread.Resume();//(弃用)唤醒线程, 使挂起的线程重新开始执行, 和Suspend()对应
    iii.	thread.Abort(); //线程终止
                try
                {
                    thread.Abort();//销毁,方式是抛异常   也不建议再使用    不一定及时/有些动作发出收不回来(比如子线程向数据库发出一个查询命令, 此时命令已经发出, 但是数据库还没有返回来值, 但是主线程强制子线程停止了, 然后数据库返回来的值就没有人接收了)
                    //就像你正在跑步, 人后旁边有人说停; 你从听到停, 到真正的停下来, 还是处于跑的状态
                   // 使用abort一定要使用try catch来处理
                }
                catch (Exception)
                {
                    //
                    Thread.ResetAbort();//取消Abort异常, 然后继续计算
                }
    
                //线程等待
    iv.	thread.Join();//当前线程等待thread完成, 当前线程就是谁执行这句话, 谁就是当前线程, 在这里当前线程就是主线程了, 因为主线程在执行这句话
                thread.Join(500);//最多等500; 当前线程等待500毫秒
                Console.WriteLine("等待500ms");
    
                // ThreadState.Running //启动线程
                while (thread.ThreadState != ThreadState.Stopped)
                {
                    Thread.Sleep(100);//当前线程 休息100ms  
                }
    v.	关于jion和sleep    
                //jion是实实在在的等待, 占据cpu;  像上面的示例, thread.jion(500), 那么这个时候, 其实thread还是在做自己的事情. jion就会在这里等一段时间(或者等thread把活干完); 此时是有两个线程并发运行的; 一个thread的线程, 一个jion的线程
                //sleep表示睡眠, 把当前线程的上下文保存着, 把cpu的时间片交出去, cpu可以去处理其他事情
    
    
    vi.	前台线程和后台线程的区别
                ////Console.WriteLine(thread.IsBackground);
                ////默认是前台线程,启动之后一定要完成任务的,阻止进程退出; 也就是说, 就算你的应用程序被关闭了, 但是前台线程还是要坚持把它的事情做完之后才会停止自己
                ////thread.IsBackground = true;//指定后台线程:随着进程退出, 也就是说程序关闭后, 后台线程也停止了
    
    
    vii.	线程优先级:
               thread.Priority = ThreadPriority.Highest;//线程优先级  
     ////CPU会优先执行标记为Highest的线程,    但是并不代表说Highest就一定最先把事情处理完, 只能说CPU会优先为这个线程分配时间片
    

    三.   使用ThreadPool

    a)         推出ThreadPool的原因:

    i.	在thread中提供了太多的API, 又是挂起, 又是终止, 又是休眠, 又是优先级, 各种各样乱七八糟, 但是又不是真真正正的真的能准确的操控
    ii.	于是就到2.0之后,Thread就被替换成了ThreadPool, 把所有的该简化的都简化了,什么API都没有了. 也没有销毁, 也没有挂起, 更没有暂停; 但是也可以将线程进行重用, 避免重复的创建和销毁, 线程被使用完之后, 会放回池子, 下次继续使用
    

    b)         使用线程池ThreadPool的方式启动多线程, 代码如下:

    //池→容器; 线程池就是线程的容器, 当在一个系统中, 反复的使用不同的资源的时候, 但是这些资源使用完需要二次创建(销毁)的成本太高的时候, 就要考虑使用池技术
                //池技术就是享元模式的精华
                //QueueUserWorkItem队列用户工作项, 将用户的工作项, 放入到队列汇总
                //QueueUserWorkItem从线程池的方式来启动多线程; 这可能是启动多线程最简单的一种方式了
                ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
                ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));

    c)         设置/获取 ThreadPool中最大和最小线程数量:

     {
                    //对线程加以限制 workerThreads→线程池中最大的工作线程数据
                    //workerThreads →线程池中的最大线程数, 这个是工作线程, 平时启动的线程一般都是基于工作线程的, 如果超过了将会被排队
                    //completionPortThreads表示线程池中异步i/o线程的最大数目
                    ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
                    Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");
                }
                {
                    //线程池内最小线程数; 默认情况下, 最小的线程数, 好像是跟CPU有关; 比如4核8线程的, 那么这里就是8和8; 我的就是4和4
                    ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
                    Console.WriteLine($"GetMinThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");
                } 
                // 有get就有set 设置最大线程数  ThreadPool的最大线程数也会影响这Task的线程
                ThreadPool.SetMaxThreads(16, 16);
                // 设置最小线程数
                ThreadPool.SetMinThreads(8, 8); //实际操作来看, 最小的会受到影响 
                //设置完成后, 再次打印一次
                {
                    ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
                    Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");
                }
                {
                    ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
                    Console.WriteLine($"GetMinThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");
                }
    

     

    d)         在ThreadPool中实现线程等待

    ////ThreadPool啥API都没有, 那么如何在ThreadPool中等待线程完成才往下执行呢?
                // 可以使用 ManualResetEvent(手动重启事件)类, 这个类包含了一个bool属性, 如果在初始化这个类的时候, 将其初始化为false, 则这个类实例的的WaitOne()方法将会被阻塞, 一直阻塞, 直到他的bool属性变成true;   当然, 可以通过ManualResetEvent的set方法使其变成 true ; 当然这里也可以自己定义一个变量来实现, 但是ManualResetEvent是线程安全的
                // false--WaitOne等待--Set--true--WaitOne直接过去
                // true--WaitOne直接过去--ReSet--false--WaitOne等待
                ManualResetEvent manualResetEvent = new ManualResetEvent(false); //初始化ManualResetEvent类中的变量为false
                ThreadPool.QueueUserWorkItem(t =>
                {
                    Console.WriteLine("即将开始执行DoSomethingLong函数");
                    this.DoSomethingLong("btnThreadPool_Click");
                    Console.WriteLine("执行DoSomethingLong函数完毕");
                    manualResetEvent.Set(); //将信号设置为true
                    //manualResetEvent.Reset(); //将信号设置为false
                });
                 manualResetEvent.WaitOne(); //阻塞当前线程; 如果当前manualResetEvent在主线程创建的, 那么就会阻塞主线程  但是要注意一般来说,不要阻塞线程池的线程   
    

      

    四.    使用Thread完成回调和带返回值的回调

    a)         使用Thread完成回调, 代码如下:

     /// <summary>
            /// 演示线程的回调; 启动子线程计算--完成委托后,该线程去执行后续回调委托 ; 
            /// </summary>
            /// <param name="act">第一个委托是你真的想要执行的这个方法</param>
            /// <param name="callback">第二委托是当你执行完第一个方法之后, 想要执行的回调, 其实就是在一个线程内将两个方法并列执行了一次</param>
            private void ThreadWithCallback(Action act, Action callback)
            {
                Thread thread = new Thread(() =>
                {
                    act.Invoke();
                    callback.Invoke();
                });
                thread.Start();
            }
    

     调用方法如下:

     private void btnThreads_Click(object sender, EventArgs e)
            { 
    
                this.ThreadWithCallback(() => Console.WriteLine($"这里是action  {Thread.CurrentThread.ManagedThreadId.ToString("00")}")
                                   , () => Console.WriteLine($"这里是callback  {Thread.CurrentThread.ManagedThreadId.ToString("00")}")); 
            }
    

     

    b)         使用Thread完成带返回值的回调, 代码如下:

    /// <summary>
            /// 带返回值的异步调用;     带返回的异步调用  需要获取返回值 (会卡界面的)
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="func"></param>
            /// <returns></returns>
            private Func<T> ThreadWithReturn<T>(Func<T> func)
            {
                #region 错误的写法
    
                //T t ;
                //Thread thread = new Thread(() =>
                //{
                //    t = func.Invoke(); //这里还没有执行的时候, 它已经返回了
                //});
                //thread.Start();
                //  return t;
                
                #endregion
    
                T t = default(T);
                Thread thread = new Thread(() =>
                {
                    
                    t = func.Invoke();
                });
                thread.Start();
                return () =>
                {
                   // while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(200); }
                    thread.Join();
                    return t;
                };
            }

     

    调用方法如下:

    private void withReturn_Click(object sender, EventArgs e)
            {
                
                Func<int> func = this.ThreadWithReturn<int>(() =>
                {
                    Thread.Sleep(2000);
                    return DateTime.Now.Millisecond;
                });
                Console.WriteLine("上面是异步调用, 直接就会打印这句话, 这句话不会等待2秒");
    
                int iResult = func.Invoke();
                Console.WriteLine(iResult); 
            }
    

     

  • 相关阅读:
    TreeSet集合的add()方法的源码解析
    Ubuntu下定时任务和自启动任务的部署
    基于Django的独立运行脚本开发
    python做量化交易干货分享
    使用Glide以及OkHttp集成
    Redis实现简单消息队列
    Linux下高并发socket最大连接数
    Ubuntu14.04部署pyspider的过程
    pymongo "ServerSelectionTimeoutError: No servers found yet" 错误的解决
    gitolite服务器部署中的一些坑
  • 原文地址:https://www.cnblogs.com/wxylog/p/9904977.html
Copyright © 2020-2023  润新知