• c# 多线程


    案例1

    在这里插入图片描述

    单线程与多线程对比

    单线程会卡主线程,此时会将ui界面给卡住。而多线程开启以后就好了 不会卡住主线程,且运行速度快,相当于多个同时运动。
    在这里插入图片描述
    单线程按钮

            private void singlethread_Click(object sender, EventArgs e)
            {            
                for (int i = 0; i < 5; i++)
                {
                    //委托 这样调用就和直接调用函数没有区别
                    //这样做的本质就相当于将方法包装了下 然后直接调用
                    Action action = () => this.run("单线程");//lambda
                    action.Invoke();//直接invoke 就是同步
    
                    //this.run(); 就等于上面这两步
                }
                Debug.WriteLine( "****************************");
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    多线程按钮

            //多线程测试
            /*1、单线程卡界面,多线程不卡界面
             *   线程就是执行流——卡界面就表示线程在忙碌 所以界面卡出了(单线程)——卡的是主线程(UI线程)
             *   多线程不卡界面——UI线程闲置了——因为把计算任务交给了其他线程完成——通过Task.Run实现——所以不卡界面了
             *
             *2、单线程慢 多线程快——但是资源占用的计算机资源也多(比如占用cpu资源,也就是如果将CPU占用100%了 再多线程也不会更快了)
             *
             *3、使用多线程过多 可能会导致系统卡死 而task就不会出现这种情况 因为其是原自线程池的  占用的线程数量不超过CPU的核数*3 应该就没有问题
             *
             *4、多线程的无序性——当然是不好的——出现原因
             *   因为线程是计算机资源 需要计算机给他分配 而计算机需要给电脑所有进程分配线程 —— 所以多线程的启动顺序是无序的
             *   我们通过相隔时间久来控制启动顺序,如果时间很久还行(但可能因为某些原因导致阻塞了啥的,导致时间就也不行),但是如果控制的时间不是很大 就不行 
             *   因此通过控制时间间隔控制启动顺序也不是一个好方法
             *
             */
            private void multithreading_Click(object sender, EventArgs e)
            {
                for (int i = 0; i < 5; i++)
                {
                    string name = $"第{i}个多线程";
                    Action action = () => this.run(name);//lambda
    
                    //Action action = () => this.run( $"第{i}个多线程"); 因此这样写就会出错(创建的线程会有多个名字相同的) 这就涉及到了多线程临时变量的问题
    
                    /*
                     * Task.Run这里会一瞬间就过了 具体创建时交给task线程去完成的 
                     * task线程启动时间是不确定的 因此第二种写法等到具体启动的时候 i值就已经发生了变化了
                     * 因此就会面临临时变量的问题了
                     */
                    Task.Run(action);//这样就是多线程了 本质是向操作系统要个系统 然后将这个事交给他了 然后就不用管了
    
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行函数

            private void run(string name)
            {
                long sum=0;
                Debug.WriteLine(name+"开始");//using System.Diagnostics;
                Debug.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId);
                for (int i = 0; i < 100; i++)
                {
                     Thread.Sleep(10);//是同步延迟 会阻塞线程 不能取消 实质是使当前线程休眠给定时间。
                  // Task.Delay(10);//异步延时 不会阻塞线程 实质创建一个运行给定时间的任务
                }
                Debug.WriteLine(name + "结束");
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    多线程同步

    在这里插入图片描述
    按钮

            private void MultiThreadSync_Click(object sender, EventArgs e)
            {
                //通过下面这段代码 打印的信息就可以看出线程的无序性 
                //打印的顺序并是不第一条 第二条 第三条这样
                List<Task> tasklist = new List<Task>();
                tasklist.Add(Task.Run(() => this.coding("第一条")));
                tasklist.Add(Task.Run(() => this.coding("第二条")));
                tasklist.Add(Task.Run(() => this.coding("第三条")));
                tasklist.Add(Task.Run(() => this.coding("第四条")));
    
                /*     1
                 * 这块是会阻塞线程的
                //阻塞到任意一个task完成——不卡界面的特性丢失了,因为主线程被阻塞在了这里
                Task.WaitAll(tasklist.ToArray());
                Debug.WriteLine("完成了其中任意一条");
    
                //阻塞到全部task都完成——不卡界面的特性丢失了,因为主线程被阻塞在了这里
                Task.WaitAll(tasklist.ToArray());
                Debug.WriteLine("全部完成");
                */
    
    
                /*      2
                //不要阻塞主线程,但是又能在全部/任意任务完成后,执行一个动作——比如写日志
                //使用方法 就是使用Task.Run将上面那段包进去 就是将这其中的动作交给一个线程(就会阻塞这个线程)来完成 所以主线程就不卡了 
                //但是不推荐用这个 因为包来包去 容易晕 包了好几层以后就很难看懂了 两层还好
                Task.Run(() =>
                {
                    Task.WaitAll(tasklist.ToArray());
                    Debug.WriteLine("完成了其中任意一条");
                 
                    Task.WaitAll(tasklist.ToArray());
                    Debug.WriteLine("全部完成");
                });
               */
    
                //这样就不会阻塞了
                tasklist[0].ContinueWith(t => Debug.WriteLine("第一个多线程的回调函数"));//task的回调
    
                Task.Factory.ContinueWhenAny(tasklist.ToArray(),t => Debug.WriteLine("任意一个多线程完成的回调函数"));//任意一个完成的回调函数
                Task.Factory.ContinueWhenAll(tasklist.ToArray(), t => Debug.WriteLine("全部多线程完成的回调函数"));//全部完成的回调函数
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
            private void coding(string name)
            {
                Debug.WriteLine("多线程同步"+name + "开始");//using System.Diagnostics;
                Debug.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId);
               
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(100);//是同步延迟 会阻塞线程 不能取消 实质是使当前线程休眠给定时间。
                                     // Task.Delay(10);//异步延时 不会阻塞线程 实质创建一个运行给定时间的任务
                }
                Debug.WriteLine("多线程同步" + name + "结束");
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    案例2 红绿灯(后台线程的使用)

    视频
    也就是主界面上的倒计时 显示放在后台 不然一直卡着 会卡死主界面的、
    知识要点:

    • 掌握backgroundWorker控件的使用(也就是后台线程)
    • 掌握sleep方法暂停线程 也就是睡眠
    • 掌握invoke方法 解决不同线程的控件调用问题
    • 窗体自带一个OnPaint事件用来重绘winform的界面
    • 界面的刷新 使用update方法、显示使用show方法
    • 掌握try catch方法 异常捕获就在catch中处理

    在这里插入图片描述
    在这里插入图片描述

    Form1_Load

            private void Form1_Load(object sender, EventArgs e)
            {
                /*
                //这样显示不是一直都在 因为界面是会刷新的 刷新了就没有了  因此将其写到重绘事件里面
                Graphics g=panel1.CreateGraphics();//在panel1里面创建画布
                g.DrawEllipse(new Pen(Color.Green),0,0,40,40);//画圆  
                */
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    OnPaint 应该每次刷新界面都会调用 用来不断的将画的图 画到界面上

            Graphics g;
            //窗体自带一个OnPaint事件用来重绘winform的界面
            protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint(e);
                 g = panel1.CreateGraphics();//在panel1里面创建画布
                g.DrawEllipse(new Pen(Color.Green), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
                Brush brush = new SolidBrush(Color.Green);
                g.FillEllipse(brush, 0, 0, 40, 40);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    开始按钮 判断是否打开 然后打开后台线程

            int count = 10;
            private void LightStartbut_Click(object sender, EventArgs e)
            {
                /*
                //这样就可以实现界面 红绿灯倒数值不断减1  但是会将界面卡死
                // 解决方法 使用后台线程去控制计算
                while (true)
                {
                    count--;
                    label2.Text = count.ToString();
                    //停留1s
                    Thread.Sleep(1000);
                    //刷新界面  
                    Update();
                }*/
    
    			//线程运行结束了 就需要重新开启
                //判断线程是否已经被开启
                if(!backgroundWorker1.IsBusy)
                {
                //应该是需要启动起来才能用,不是创建了就能用的
                    //开启后台线程运行
                    backgroundWorker1.RunWorkerAsync();//无法同时开启多个 同一时间只能被开启一次
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    后台进行处理 双击backgroundWorker控件就可以进入

            //后台线程 可以用在一些复杂计算 死循环 会卡死主界面的操作时
            //后台线程控件具体使用DoWork来进行计算
            //其他线程控制主线程需要使用  invoke 方法
            private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
            {
            //这里是需要加while的 不然运行一遍就出去了
                while (true)
                {
                    count--;
                    try
                    {
                        //invoke 方法 会在当前的这个线程里面 往上查找 直到找到当前控件所在的线程 给他的值进行更新
                        //实现了 不同线程调用另一个主线程控件改变值的方法
                        Invoke(new Action(() =>
                        {
                            label1.Text = count.ToString();
    
                            //刷新界面  
                            Update();
                        }));
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                        throw;
                    }
    
                    if(count<4&&count>0)
                    {
                        g.DrawEllipse(new Pen(Color.Yellow), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
                        Brush brush = new SolidBrush(Color.Yellow);
                        g.FillEllipse(brush, 0, 0, 40, 40);
                    }
                    else if(count<=0)
                    {
                        g.DrawEllipse(new Pen(Color.Red), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
                        Brush brush = new SolidBrush(Color.Red);
                        g.FillEllipse(brush, 0, 0, 40, 40);
                        break;
                    }
    
                    //Invoke(new EventHandler(delegate
                    //{
                    //    label1.Text = count.ToString();
    
                    //    //刷新界面  
                    //    Update();
                    //}));
    
                    //停留1s
                    Thread.Sleep(1000);
    
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    关闭之后将后台线程关闭

            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                //这样就可以在后台进程运行是 关闭程序都没有问题了
                //在做关闭之前 需要先在属性界面选择可以支持取消这个功能
                backgroundWorker1.CancelAsync();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    使用示例

                        //委托 这样调用就和直接调用函数没有区别
                        //这样做的本质就相当于将方法包装了下 然后直接调用
                        Action action = () => this.run();//lambda
                      Task.Run(action);
    
    • 1
    • 2
    • 3
    • 4
            public void run()
            {
                try
                {
                    //invoke 方法 会在当前的这个线程里面 往上查找 直到找到当前控件所在的线程 给他的值进行更新
                    //实现了 不同线程调用另一个主线程控件改变值的方法
                    Invoke(new Action(() =>
                    {
                       //在Invoke这个里面使用 sleep 也是会卡死主线程的
                       //如果不想卡死主线程,可以将其放到Invoke外面
                        Thread.Sleep(2000);
                        uiButton2.Enabled = true;
                    }));
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    throw;
                }
    
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    或者这样写

                            Task.Run(async () => 
                            {
                                while (true )
                                {
                                    await Task.Delay(1000);
                                    //3、读取寄存器数据 并写入NitextBox1中
                                    ushort[] values = master.ReadHoldingRegisters(1, 0, 1);//光标放在函数上面会显示要填的参数的含义。slaveID 0号寄存器 读1个 这个函数返回的是一个uchort数组
                                    try //通过断点调试 发现了卡在了这里。通过try和catch的方式,就将出现的异常捕获了
                                    {
                                        NitextBox1.Text = values[0].ToString();//NitextBox1就是第一个textBox的name
                                    }
                                    catch (Exception ex) 
                                    { 
                                        MessageBox.Show(ex.Message);//错误的原因是进程间操作无效 使用一个简单的操作 就是关闭检测进程间的合法性
                                    }
    
                                }       
                            });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    编译kubeadm使生成证书有效期为100年
    C++回顾录
    生成.pkl文件,读取.pkl文件的内容
    【节能学院】智能操控装置在高压开关柜的应用
    某马机房预约系统 C++项目(二) 完结
    sysfs 文件系统
    Spring框架(九):Spring注解开发Annotation
    计算机毕设(附源码)JAVA-SSM计算机等级考试管理系统
    信息论与编码P02014217刘伟
    Elasticsearch模拟网络丢包
  • 原文地址:https://blog.csdn.net/chengcao123/article/details/127865025