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

猜你喜欢

转载自www.cnblogs.com/Dewumu/p/11868511.html