• 20181106_线程之异常_取消_变量_安全Lock


    一. 线程的异常处理:

     try
                {
                    TaskFactory taskFactory = new TaskFactory();
                    List<Task> taskList = new List<Task>();
                     
                    //线程里面的异常是被吞掉了,因为已经脱离try catch的范围了  如果真想让线程之外的Catch抓到异常, 那么只有使用 WaitAll 抓到多线程里面全部的异常
                    //实际开发: 线程里面的action不允许出现异常,自己使用try catch处理好
                    for (int i = 0; i < 20; i++)
                    {
                        string name = string.Format($"btnThreadCore_Click_{i}");
                        Action<object> act = t =>
                        {
                            try
                            {
                                Thread.Sleep(2000);
                                if (t.ToString().Equals("btnThreadCore_Click_11"))
                                {
                                    throw new Exception(string.Format($"{t} 执行失败"));
                                }
                                if (t.ToString().Equals("btnThreadCore_Click_12"))
                                {
                                    throw new Exception(string.Format($"{t} 执行失败"));
                                }
                                Console.WriteLine("{0} 执行成功", t);
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine($"Exception:{ex.Message}"); //
                            }
                        };
                        taskList.Add(taskFactory.StartNew(act, name));
                    }
                   // Task.WaitAll(taskList.ToArray());使用 WaitAll 捕获多线程里面全部的异常; 但是也不能总是WaitAll, 并且很多业务也不能使用WaitAll来处理
                    
                }
                catch (AggregateException aex) //专门处理多线程的异常, 里面的异常有多项
                {
                    foreach (var item in aex.InnerExceptions)
                    {
                        Console.WriteLine(item.Message);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
    

     

    二. 线程的取消, 

    a)   场景: 多个线程并发,某个失败后,希望通知别的线程,都停下来, 尽量使用CancellationTokenSource, 不要自己创建bool变量

     try
                {
                    TaskFactory taskFactory = new TaskFactory();
                    List<Task> taskList = new List<Task>();
    
              
                     //多个线程并发,某个失败后,希望通知别的线程,都停下来
                     //task是外部无法中止(中间停止),Thread.Abort不靠谱,因为线程是OS的资源,无法掌控啥时候取消
                     //线程的停止: 线程自己停止自己--公共的访问变量--修改它---线程不断的检测它(延迟少不了)
                     //CancellationTokenSource去标志任务是否取消  Cancel取消   IsCancellationRequested  是否已经取消了
                     //Token 启动Task的时候传入,那么如果Cancel了,这个任务会放弃启动,抛出一个异常
                     // 当一个线程不执行, 后面的线程全部都不能再执行了
                     CancellationTokenSource cts = new CancellationTokenSource();//bool值 //bool flag = true; 线程安全的
                    for (int i = 0; i < 40; i++)
                    {
                        string name = string.Format("btnThreadCore_Click{0}", i);
                        Action<object> act = t =>
                        {
                            try
                            {
                                //if (cts.IsCancellationRequested)
                                //{
                                //    Console.WriteLine("{0} 取消一个任务的执行", t);
                                //}
                                Thread.Sleep(2000);
                                if (t.ToString().Equals("btnThreadCore_Click11"))
                                {
                                    throw new Exception(string.Format("{0} 执行失败", t));
                                }
                                if (t.ToString().Equals("btnThreadCore_Click12"))
                                {
                                    throw new Exception(string.Format("{0} 执行失败", t));
                                }
                                if (cts.IsCancellationRequested)//启动40个线程, 每一个线程都检查这个信号量, 是否改任务已经被取消
                                {
                                    Console.WriteLine("{0} 放弃执行", t);
                                    return;
                                }
                                else
                                {
                                    Console.WriteLine("{0} 执行成功", t);
                                }
                            }
                            catch (Exception ex)
                            {
                                cts.Cancel(); //当执行抛出异常后, (t.ToString().Equals("btnThreadCore_Click11")); 将信号量设置为false, 表示取消任务
                                Console.WriteLine(ex.Message);
                            }
                        };
                        taskList.Add(taskFactory.StartNew(act, name, cts.Token));
                        //如果一个线程在启动的时候标识了cts.Token, 当cts.Cancel()时, 如果这个线程还没有启动, 则会被标识为取消一个任务执行; 如果这个线程已经启动, 但是还没有执行, 那么会被放弃执行 
                    }
                    Task.WaitAll(taskList.ToArray());
                     
                }
                catch (AggregateException aex) //专门处理多线程的异常
                {
                    foreach (var item in aex.InnerExceptions)
                    {
                        Console.WriteLine(item.Message);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
    

     

    三. 多线程临时变量

    for (int i = 0; i < 5; i++)
                    {
                        Task.Run(() =>
                          {
                              Thread.Sleep(100);
                              Console.WriteLine(i);  //打印结果 5个5
                          });
                    }
    
                    for (int i = 0; i < 5; i++)
                    {
                        int k = i;
                        Task.Run(() =>
                        {
                            Thread.Sleep(100);
                            Console.WriteLine(k); //打印结果 4,3,1,2,0
                        });
                    }
    
                    //i最后是5      全程就只有一个i  等着打印的时候,i==5
                    //k             全程有5个k   分别是0 1 2 3 4 
                    //k在外面声明   全程就只有一个k    等着打印的时候,k==4
                    int k = 0;
                    for (int i = 0; i < 5; i++)
                    {
                        //    int k = i;
                        k = i;
                        new Action(() =>
                        {
                            Thread.Sleep(100);
                            Console.WriteLine($"k={k} i={i}");
                        }).BeginInvoke(null, null);
                    }
    

     

    四. 线程安全:

    try
                {
                    TaskFactory taskFactory = new TaskFactory();
                    List<Task> taskList = new List<Task>();
     
                    //如何判断何时加锁: 线程内部声明的变量, 由于不共享的是线程安全的; 但是在线程外部操作的资源由于共享, 则就会造成线程不安全. 比如全局变量/数据库的某个值/磁盘文件
                    int TotalCountIn = 0;//TotalCountIn属于线程外部变量, 会有线程安全有问题
                    for (int i = 0; i < 10000; i++)//i属于线程外部变量, 会有线程安全有问题
                    {
                        int newI = i;//newI 属于线程内部变量, 不会有线程安全有问题
                        taskList.Add(taskFactory.StartNew(() =>
                        {
                           //值类型不能lock
                            lock (btnThreadCore_Click_Lock)//lock后的方法块,任意时刻只有一个线程可以进入  
                            //只能锁引用类型,占用这个引用链接   不要用string 因为享元  , 可能导致锁的是同一块内存区域
                            {   //这里就是单线程
                                 
                                this.TotalCount += 1;
                                TotalCountIn += 1;
                                this.IntList.Add(newI);
                            }
    
                        }));
                    }
                    Task.WaitAll(taskList.ToArray());
                    Console.WriteLine(this.TotalCount);
                    Console.WriteLine(TotalCountIn);
                    Console.WriteLine(this.IntList.Count());
                    #endregion 
                }
                catch (AggregateException aex) //专门处理多线程的异常
                {
                    foreach (var item in aex.InnerExceptions)
                    {
                        Console.WriteLine(item.Message);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
    

    五.   关于Lock变量的写法解释:

    //为什么锁的变量要这样写private static readonly object btnThreadCore_Click_Lock = new object();
    //private  防止外面也去锁     
    //static 全场唯一  
    //readonly不准修改, 如果不是readonly有可能会在锁的代码里面修改它  
    //object表示引用, 因为lock不能锁值类型

     

     六. 为什么不推荐锁this

     

    //lock (this)
    //{
    //    //this form1的实例  每次实例化是不同的锁,同一个实例是相同的锁
    //    //但是这个实例别人也能访问到,别人也能锁定
    //    //最好不要锁this
    //}
    

     

    七. Lock到底是个什么? 

    //Monitor.Enter(btnThreadCore_Click_Lock);
    //lock 就是一个语法糖, 类似于monitor的写法, 也就是说可以将lock{ }块中的语句, 放到Monitor的Enter和exit中间
    //Monitor.Exit(btnThreadCore_Click_Lock);
    Monitor.Enter(btnThreadCore_Click_Lock);
    //lock 就是一个语法糖, 类似于monitor的写法, 也就是说可以将lock{ }块中的语句, 放到Monitor的Enter和exit中间
    {   //这里就是单线程
    
         this.TotalCount += 1;
         TotalCountIn += 1;
         this.IntList.Add(newI);
    }
    Monitor.Exit(btnThreadCore_Click_Lock);
    

    八. 不使用lock解决,线程安全问题:

    //使用lock 解决的时候,因为只有一个线程可以操作数据, 没有并发, 所以解决了问题    但是牺牲了性能,所以要尽量缩小lock的范围
    
    //解决办法:1.  开发中最好不要有冲突, 能拆分就优先拆分, 比如在1亿条数据中进行操作, 那么可以先将数据拆分成不同的小块, 然后再合并成1亿条数据
    
    //2. 不使用locak, 可以使用安全队列 System.Collections.Concurrent.ConcurrentQueue来解决,   一个线程去完成操作
    

      

  • 相关阅读:
    C# 日期格式化
    MVVM框架下,WPF实现Datagrid里的全选和选择
    【转】WPF 给DataGridTextColumn统一加上ToolTip
    C# 获取当前月第一天和最后一天 计算两个日期差多少天
    WPF 弹出UserControl
    斐讯Fir302b救砖教程
    mvc 传递匿名对象
    Java HttpGet
    Java xml object 互转
    HttpClientHandler
  • 原文地址:https://www.cnblogs.com/wxylog/p/9918074.html
Copyright © 2020-2023  润新知