c# 多线程

案例1 单线程与多线程同步

在这里插入图片描述
视频教程

单线程做菜

        //按钮的线程和ui显示的线程是一个线程
        //因为暂停以后主界面就拖动不了
        private void singlethread_Click(object sender, EventArgs e)
        {
            Thread.Sleep(3000);//这个线程要暂停3s 此时主界面窗口是拖动不了的
            MessageBox.Show("素菜做好了", "友情提示");
            Thread.Sleep(3000);
            MessageBox.Show("荤菜做好了", "友情提示");
        }

thread 做菜

        //这样就不会影响到我们ui线程
        //可以直接拖动,也可以点击其他按钮,同时执行其他方法
        private void thread_Click(object sender, EventArgs e)
        {
            //里面的参数是一个委托或者说是一个方法
            Thread t = new Thread(() =>
            {
                Thread.Sleep(3000);//这个线程要暂停3s 
                MessageBox.Show("素菜做好了", "友情提示");
                Thread.Sleep(3000);
                MessageBox.Show("荤菜做好了", "友情提示");
            });
            t.Start();
        }

task做菜 更推荐用这个

        //更推荐用任务来实现多线程
        //task底层使用的也是thread,但是他提供了很多线程相关的管理,比如线程池等 他的性能会更好 管理会更方便,所以推荐使用task
        private void TaskBtn_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                Thread.Sleep(3000);//这个线程要暂停3s 
                MessageBox.Show("素菜做好了", "友情提示");
                Thread.Sleep(3000);
                MessageBox.Show("荤菜做好了", "友情提示");
            });
        }
        //同时做多道菜,这两个是同时完成的
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                Thread.Sleep(3000);//这个线程要暂停3s 
                MessageBox.Show("素菜做好了", "友情提示");
            });
            Task.Run(() =>
            {
                Thread.Sleep(3000);
                MessageBox.Show("荤菜做好了", "友情提示");
            });
        }

同步完成 包含完成后回调函数

     public void cook()
     {
         Thread.Sleep(4000);
         MessageBox.Show("荤菜做好了", "友情提示");
     }
     private  void button2_Click(object sender, EventArgs e)
     {

         这样写的话 直接跳到 菜都做好了吃饭。 开启了线程不管他有没有执行完毕,直接运行后面的代码了
         //Task.Run(() =>
         //{
         //    Thread.Sleep(3000);//这个线程要暂停3s 
         //    MessageBox.Show("素菜做好了", "友情提示");
         //    Thread.Sleep(3000);
         //    MessageBox.Show("荤菜做好了", "友情提示");
         //});
         //MessageBox.Show("菜都做好了,大家快来吃饭", "提示");

         使用关键字await以后 程序头会自动添加async(异步方法),就不会卡界面了,与我们的ui线程不是同一个线程了
         //await Task.Run(() =>
         //{
         //    Thread.Sleep(3000);//这个线程要暂停3s 
         //    MessageBox.Show("素菜做好了", "友情提示");
         //    Thread.Sleep(3000);
         //    MessageBox.Show("荤菜做好了", "友情提示");
         //});
         上面的线程运行完以后再执行
         //MessageBox.Show("菜都做好了,大家快来吃饭", "提示");

         List<Task> list = new List<Task>();
         list.Add(Task.Run(() => 
         {
             Thread.Sleep(1000);//这个线程要暂停3s 
             MessageBox.Show("素菜做好了", "友情提示");
         }));
         list.Add(Task.Run(() => 
         {
             cook();
         }));
         //设置一个回调函数 两个都完成了 执行
         Task.WhenAll(list).ContinueWith(t=>
         {
             MessageBox.Show("菜都做好了,大家快来吃饭", "提示");
         });
         其中任意一个完成了的回调函数
         //Task.WhenAny(list).ContinueWith(t => 
         //{
         //    MessageBox.Show("其中一个完成了");
         //});
         指定其中一个完成的回调函数
         //list[0].ContinueWith(t => {
         //    MessageBox.Show("第一个完成了");
         //});
         //先打印这句话,然后再打印做好菜 吃饭啥的
         MessageBox.Show("上面那个是回调函数");

     }

案例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);//画圆  
            */
        }

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);
        }

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

        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();//无法同时开启多个 同一时间只能被开启一次
            }
        }

后台进行处理 双击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);

            }
        }

关闭之后将后台线程关闭

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

在这里插入图片描述

后台线程的使用2

包含了 后台线程的数据传递,开启,取消,运行进度

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

启动设置

        public Form1()
        {
            InitializeComponent();
            //可以直接在属性栏设置
            backgroundWorker1.WorkerReportsProgress = true;//我们的精度报告是否允许发送
            backgroundWorker1.WorkerSupportsCancellation = true; //是否允许取消   
        }

dowork事件

        //不需要里面再创建一个 不会卡ui线程的
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            //新线程 新任务
            BackgroundWorker worker = sender as BackgroundWorker;
            for (int i = 1; i <= 100; i++)
            {
                if (worker.CancellationPending == true)
                {
                    e.Cancel = true;//更改运行返回状态 并不能取消
                    break;
                }
                else
                {
                    Thread.Sleep(50);//这里可以替换为工作代码
                    worker.ReportProgress(i);//提交了一个报告 就会引发一个事件
                }
            }
            e.Result = e.Argument;


        }

启动

        //启动按钮
        private void button1_Click(object sender, EventArgs e)
        {
            //线程运行结束了 就需要重新开启
            //判断线程是否已经被开启 已经启动了 是不能再启动的
            if (!backgroundWorker1.IsBusy)
            {
                //需要启动起来才能用,不是创建了就能用的
                //开启后台线程运行
                //backgroundWorker1.RunWorkerAsync();//无法同时开启多个 同一时间只能被开启一次
                backgroundWorker1.RunWorkerAsync("ni");//可以有参数 可以没有参数
            }
        }

取消

       //取消按钮
        private void button2_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.WorkerSupportsCancellation==true)
            {
                backgroundWorker1.CancelAsync();
            }
        }

进度变化事件

//ProgressChanged事件处理程序 在创建backgroundWorker线程上执行 也就是在ui线程中执行的
//这样就不需要写invoke了

        //在属性中 事件那里  双击进来
        //进度每次发生变化 都会进到这里来 这个事件处理程序是异步调用的
        //ProgressChanged事件处理程序 在创建backgroundWorker线程上执行 也就是在ui线程中执行的
        //这样就不需要写invoke了
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
            label1.Text=(e.ProgressPercentage.ToString()+"%");
        }

处理结束事件

        //所有事情都处理完了以后的处理
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled==true)
            {
                label2.Text = "被取消了";
            }
            else if (e.Error!=null)
            {
                label2.Text = "错误"+e.Error.Message;
            }
            else
            {
                label2.Text = "完成!Resule=" + e.Result;
            }
        }

使用示例

                    //委托 这样调用就和直接调用函数没有区别
                    //这样做的本质就相当于将方法包装了下 然后直接调用
                    Action action = () => this.run();//lambda
                  Task.Run(action);
        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;
            }

        }

或者这样写

                        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);//错误的原因是进程间操作无效 使用一个简单的操作 就是关闭检测进程间的合法性
                                }

                            }       
                        });

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值