• C# 多线程总结 异常处理 线程取消 锁(lock)


    那么什么时候能用多线程? 任务能并发的时候

    多线程能干嘛?提升速度/优化用户体验

    网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll
    列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了

    现实实例

    多人合作开发---多线程--提升效率/性能

     1               {
     2                 TaskFactory taskFactory = new TaskFactory();
     3                 List<Task> taskList = new List<Task>();
     4                 taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A"));
     5                 taskList.Add(taskFactory.StartNew(o=> Coding("B", "    DBA"), "B"));
     6                 taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C"));
     7                 taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D"));
     8                 taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E"));
     9 
    10                 //谁第一个完成,获取一个红包奖励
    11                 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
    12                 //实战作业完成后,一起庆祝一下
    13                 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
    14                 //ContinueWhenAny  ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程
    15 
    16 
    17                 //阻塞当前线程,等着任意一个任务完成
    18                 Task.WaitAny(taskList.ToArray());//也可以限时等待
    19                 Console.WriteLine("准备环境开始部署");
    20                 //需要能够等待全部线程完成任务再继续  阻塞当前线程,等着全部任务完成
    21                 Task.WaitAll(taskList.ToArray());
    22                 Console.WriteLine("5个模块全部完成后,集中调试");
    23 
    24                 //Task.WaitAny  WaitAll都是阻塞当前线程,等任务完成后执行操作
    25                 //阻塞卡界面,是为了并发以及顺序控制
    26             }
    View Code
     1         /// <summary>
     2         /// 模拟Coding过程
     3         /// </summary>
     4         /// <param name="name"></param>
     5         /// <param name="projectName"></param>
     6         private static string Coding(string name, string projectName)
     7         {
     8             Console.WriteLine($"****************Coding Start  {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
     9             long lResult = 0;
    10             for (int i = 0; i < 1_000_000_000; i++)
    11             {
    12                 lResult += i;
    13             }
    14             Console.WriteLine($"****************Coding   End  {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
    15             return name;
    16         }
    View Code

    多线程异常处理

     1             #region 多线程异常处理
     2             {
     3                 try
     4                 {
     5 
     6                     List<Task> taskList = new List<Task>();
     7                     for (int i = 0; i < 100; i++)
     8                     {
     9                         string name = $"btnThreadCore_Click_{i}";
    10                         taskList.Add(Task.Run(() =>
    11                         {
    12                             if (name.Equals("btnThreadCore_Click_11"))
    13                             {
    14                                 throw new Exception("btnThreadCore_Click_11异常");
    15                             }
    16                             else if (name.Equals("btnThreadCore_Click_12"))
    17                             {
    18                                 throw new Exception("btnThreadCore_Click_12异常");
    19                             }
    20                             else if (name.Equals("btnThreadCore_Click_38"))
    21                             {
    22                                 throw new Exception("btnThreadCore_Click_38异常");
    23                             }
    24                             Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    25                         }));
    26                     }
    27                     //多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
    28                     //那线程异常哪里去了? 被吞了,
    29                     //假如我想获取异常信息,还需要通知别的线程
    30                     Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
    31                 }
    32                 catch (AggregateException aex)//2 需要try-catch-AggregateException
    33                 {
    34                     foreach (var exception in aex.InnerExceptions)
    35                     {
    36                         Console.WriteLine(exception.Message);
    37                     }
    38                 }
    39                 catch (Exception ex)//可以多catch  先具体再全部
    40                 {
    41                     Console.WriteLine(ex);
    42                 }
    43                 //线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
    44                 //工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作
    45             }
    46             #endregion
    View Code

    多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;线程异常哪里去了? 被吞了

    多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息 ,通知别的线程

    线程取消

     1               {
     2                 CancellationTokenSource cts = new CancellationTokenSource();
     3                 var token = cts.Token; cts.Cancel();
     4                 CancellationTokenSource cts2 = new CancellationTokenSource();
     5                 var token2 = cts2.Token;
     6                 List<Task> taskList = new List<Task>();
     7                 for (int i = 0; i < 10; i++)
     8                 {
     9                     int k = i;
    10                     switch (i%5)
    11                     {
    12                         case 0:
    13                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break;
    14                         case 1: 
    15                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break;
    16                         case 2: 
    17                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break;
    18                         case 3:
    19                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break;
    20                         case 4:
    21                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4");
    22                                 throw new Exception("throw new Exception");
    23                             })); break;
    24                     }
    25                 }
    26                 //Thread.Sleep(500);
    27                 cts2.Cancel();
    28                 try
    29                 {
    30                     Task.WaitAll(taskList.ToArray());
    31                 }catch(AggregateException ae)
    32                 {
    33                     foreach (var item in ae.InnerExceptions)
    34                     {
    35                         Console.WriteLine($"{item.GetType().Name}:{item.Message}");
    36                     }
    37                 }
    38                 Console.WriteLine("**********************************");
    39                 foreach (var item in taskList)
    40                 {
    41                     Console.WriteLine($"Id:{item.Id},Status:{item.Status}");
    42                     if (item.Exception != null)
    43                     {
    44                         foreach (var ex in item.Exception.InnerExceptions)
    45                         {
    46                             Console.WriteLine($"{ex.GetType().Name}:{ex.Message}");
    47                         }
    48                     }
    49                 }
    50             }
    View Code

    运行上面的代码,有四个任务被取消,取消注释,则有两个任务被取消

    线程安全

    如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的
    线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改

    Lock

    1、Lock解决多线程冲突

    Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着 

    推荐锁是private static readonly object

    A 不能是Null,可以编译不能运行;

    B 不推荐lock(this),外面如果也要用实例,就冲突了

     1     public class LockHelper
     2     {
     3         public void Show()
     4         {
     5             LockTest test = new LockTest();
     6             Console.WriteLine(DateTime.Now);
     7             Task.Delay(10).ContinueWith(t =>
     8             {
     9                 lock (test)
    10                 {
    11                     Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
    12                     Thread.Sleep(5000);
    13                     Console.WriteLine($"*********End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
    14                 }
    15             });
    16             test.LockThis();
    17         }
    18     }
    19     public class LockTest
    20     {
    21         private int lockthis;
    22         public void LockThis()
    23         {
    24             lock (this)
    25             //递归调用,lock this  会不会死锁? 不会死锁!
    26             //这里是同一个线程,这个引用就是被这个线程所占据
    27             {
    28                 Thread.Sleep(1000);
    29                 this.lockthis++;
    30                 if (this.lockthis < 10)
    31                     this.LockThis();
    32                 else
    33                     Console.WriteLine($"This is  {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    34             }
    35         }
    36     }
    View Code

    这里LockThis自身递归调用不会死锁,这个引用被当前线程占用,但当另外的实例要使用时就冲突了,必须等待LockThis执行完成后,释放当前实例,外面的实例才能被调用

    C 不应该是string; string在内存分配上是重用的,会冲突

     1             {
     2                 LockTest test = new LockTest();
     3                 Console.WriteLine(DateTime.Now);
     4                 string lockString = "lockString";
     5                 Task.Delay(1000).ContinueWith(t =>
     6                 {
     7                     lock (lockString)
     8                     {
     9                         Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
    10                         Thread.Sleep(5000);
    11                         Console.WriteLine($"****lockString End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
    12                     }
    13                 });
    14                 test.LockString();
    15             }
    16     public class LockTest
    17     {
    18         private int lockthis;
    19         public void LockThis()
    20         {
    21             lock (this)
    22             //递归调用,lock this  会不会死锁? 不会死锁!
    23             //这里是同一个线程,这个引用就是被这个线程所占据
    24             {
    25                 Thread.Sleep(1000);
    26                 this.lockthis++;
    27                 if (this.lockthis < 10)
    28                     this.LockThis();
    29                 else
    30                     Console.WriteLine($"This is  {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    31             }
    32         }
    33 
    34         private string lockString= "lockString";
    35         public void LockString()
    36         {
    37             lock (lockString)
    38             {
    39                 Thread.Sleep(2000);
    40                 Console.WriteLine($"This is  {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    41                 Thread.Sleep(2000);
    42             }
    43         }
    44     }
    View Code

    String类型在内存分配上按享元模式设计的,某个字符串被占用,其他线程就必须等待字符串释放后才能使用

    D Lock里面的代码不要太多,这里是单线程的

    2、线程安全集合

    System.Collections.Concurrent.ConcurrentQueue<T>

    3、 数据分拆,避免多线程操作同一个数据;又安全又高效

     1         private int _sync = 0;
     2         private int _async = 0;
     3         private List<int> listInt = new List<int>();
     4         private static readonly object lockObject = new object();
     5         public void LockObject()
     6         {
     7             for (int i = 0; i < 1000; i++)
     8             {
     9                 this._sync++;
    10             }
    11             for (int i = 0; i < 1000; i++)
    12             {
    13                 Task.Run(() => this._async++);
    14             }
    15             for (int i = 0; i < 1000; i++)
    16             {
    17                 int k = i;
    18                 Task.Run(() => this.listInt.Add(k));
    19             }
    20             Thread.Sleep(5 * 1000);
    21             Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
    22         }
    View Code

    运行上面的代码发现_sync=1000  _async与listInt集合个数都少于1000

     1         public void LockObject()
     2         {
     3             for (int i = 0; i < 1000; i++)
     4             {
     5                 this._sync++;
     6             }
     7             for (int i = 0; i < 1000; i++)
     8             {
     9                 Task.Run(() => {
    10                     lock (lockObject)
    11                     {
    12                         this._async++;
    13                     }
    14                 });
    15             }
    16             for (int i = 0; i < 1000; i++)
    17             {
    18                 int k = i;
    19                 Task.Run(() => {
    20                     lock (lockObject)
    21                     {
    22                         this.listInt.Add(k);
    23                     }
    24                 });
    25             }
    26             Thread.Sleep(5 * 1000);
    27             Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
    28         }
    View Code

    使用lock包装后 _async与listInt集合个数都为1000, 使用lock后 只有一个线程才能进入lock方法块内,相当于把程序又变回了单线程

    微软文档:

    lock:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement

    CancellationTokenSource:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtokensource?view=netframework-4.8

    CancellationToken:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8

  • 相关阅读:
    做人做事
    不骄不躁
    争取
    收入
    Windows 7下的Comodo Firewall免费防火墙
    成功水平
    成家立业
    Windows无法安装到GPT格式磁盘的根本解决办法
    安装Windows10操作系统
    安装操作系统的几种方式
  • 原文地址:https://www.cnblogs.com/Dewumu/p/11868511.html
Copyright © 2020-2023  润新知