C# 多线程总结 异常处理 线程取消 锁(lock)
那么什么时候能用多线程? 任务能并发的时候
多线程能干嘛?提升速度/优化用户体验
网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll
列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了
现实实例
多人合作开发---多线程--提升效率/性能
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A"));
taskList.Add(taskFactory.StartNew(o=> Coding("B", " DBA"), "B"));
taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C"));
taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D"));
taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E")); //谁第一个完成,获取一个红包奖励
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("")}"));
//实战作业完成后,一起庆祝一下
taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("")}")));
//ContinueWhenAny ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程 //阻塞当前线程,等着任意一个任务完成
Task.WaitAny(taskList.ToArray());//也可以限时等待
Console.WriteLine("准备环境开始部署");
//需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成
Task.WaitAll(taskList.ToArray());
Console.WriteLine("5个模块全部完成后,集中调试"); //Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操作
//阻塞卡界面,是为了并发以及顺序控制
}
/// <summary>
/// 模拟Coding过程
/// </summary>
/// <param name="name"></param>
/// <param name="projectName"></param>
private static string Coding(string name, string projectName)
{
Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = ;
for (int i = ; i < 1_000_000_000; i++)
{
lResult += i;
}
Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
return name;
}
多线程异常处理
#region 多线程异常处理
{
try
{ List<Task> taskList = new List<Task>();
for (int i = ; i < ; i++)
{
string name = $"btnThreadCore_Click_{i}";
taskList.Add(Task.Run(() =>
{
if (name.Equals("btnThreadCore_Click_11"))
{
throw new Exception("btnThreadCore_Click_11异常");
}
else if (name.Equals("btnThreadCore_Click_12"))
{
throw new Exception("btnThreadCore_Click_12异常");
}
else if (name.Equals("btnThreadCore_Click_38"))
{
throw new Exception("btnThreadCore_Click_38异常");
}
Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("")}");
}));
}
//多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
//那线程异常哪里去了? 被吞了,
//假如我想获取异常信息,还需要通知别的线程
Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
}
catch (AggregateException aex)//2 需要try-catch-AggregateException
{
foreach (var exception in aex.InnerExceptions)
{
Console.WriteLine(exception.Message);
}
}
catch (Exception ex)//可以多catch 先具体再全部
{
Console.WriteLine(ex);
}
//线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
//工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作
}
#endregion
多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;线程异常哪里去了? 被吞了
多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息 ,通知别的线程
线程取消
{
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token; cts.Cancel();
CancellationTokenSource cts2 = new CancellationTokenSource();
var token2 = cts2.Token;
List<Task> taskList = new List<Task>();
for (int i = ; i < ; i++)
{
int k = i;
switch (i%)
{
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4");
throw new Exception("throw new Exception");
})); break;
}
}
//Thread.Sleep(500);
cts2.Cancel();
try
{
Task.WaitAll(taskList.ToArray());
}catch(AggregateException ae)
{
foreach (var item in ae.InnerExceptions)
{
Console.WriteLine($"{item.GetType().Name}:{item.Message}");
}
}
Console.WriteLine("**********************************");
foreach (var item in taskList)
{
Console.WriteLine($"Id:{item.Id},Status:{item.Status}");
if (item.Exception != null)
{
foreach (var ex in item.Exception.InnerExceptions)
{
Console.WriteLine($"{ex.GetType().Name}:{ex.Message}");
}
}
}
}
运行上面的代码,有四个任务被取消,取消注释,则有两个任务被取消
线程安全
如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的
线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改
Lock
1、Lock解决多线程冲突
Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object
A 不能是Null,可以编译不能运行;
B 不推荐lock(this),外面如果也要用实例,就冲突了
public class LockHelper
{
public void Show()
{
LockTest test = new LockTest();
Console.WriteLine(DateTime.Now);
Task.Delay().ContinueWith(t =>
{
lock (test)
{
Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
Thread.Sleep();
Console.WriteLine($"*********End {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
}
});
test.LockThis();
}
}
public class LockTest
{
private int lockthis;
public void LockThis()
{
lock (this)
//递归调用,lock this 会不会死锁? 不会死锁!
//这里是同一个线程,这个引用就是被这个线程所占据
{
Thread.Sleep();
this.lockthis++;
if (this.lockthis < )
this.LockThis();
else
Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
}
这里LockThis自身递归调用不会死锁,这个引用被当前线程占用,但当另外的实例要使用时就冲突了,必须等待LockThis执行完成后,释放当前实例,外面的实例才能被调用
C 不应该是string; string在内存分配上是重用的,会冲突
{
LockTest test = new LockTest();
Console.WriteLine(DateTime.Now);
string lockString = "lockString";
Task.Delay().ContinueWith(t =>
{
lock (lockString)
{
Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
Thread.Sleep();
Console.WriteLine($"****lockString End {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
}
});
test.LockString();
}
public class LockTest
{
private int lockthis;
public void LockThis()
{
lock (this)
//递归调用,lock this 会不会死锁? 不会死锁!
//这里是同一个线程,这个引用就是被这个线程所占据
{
Thread.Sleep();
this.lockthis++;
if (this.lockthis < )
this.LockThis();
else
Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
} private string lockString= "lockString";
public void LockString()
{
lock (lockString)
{
Thread.Sleep();
Console.WriteLine($"This is {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Thread.Sleep();
}
}
}
String类型在内存分配上按享元模式设计的,某个字符串被占用,其他线程就必须等待字符串释放后才能使用
D Lock里面的代码不要太多,这里是单线程的
2、线程安全集合
System.Collections.Concurrent.ConcurrentQueue<T>
3、 数据分拆,避免多线程操作同一个数据;又安全又高效
private int _sync = ;
private int _async = ;
private List<int> listInt = new List<int>();
private static readonly object lockObject = new object();
public void LockObject()
{
for (int i = ; i < ; i++)
{
this._sync++;
}
for (int i = ; i < ; i++)
{
Task.Run(() => this._async++);
}
for (int i = ; i < ; i++)
{
int k = i;
Task.Run(() => this.listInt.Add(k));
}
Thread.Sleep( * );
Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
}
运行上面的代码发现_sync=1000 _async与listInt集合个数都少于1000
public void LockObject()
{
for (int i = ; i < ; i++)
{
this._sync++;
}
for (int i = ; i < ; i++)
{
Task.Run(() => {
lock (lockObject)
{
this._async++;
}
});
}
for (int i = ; i < ; i++)
{
int k = i;
Task.Run(() => {
lock (lockObject)
{
this.listInt.Add(k);
}
});
}
Thread.Sleep( * );
Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
}
使用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
C# 多线程总结 异常处理 线程取消 锁(lock)的更多相关文章
- python线程互斥锁Lock(29)
在前一篇文章 python线程创建和传参 中我们介绍了关于python线程的一些简单函数使用和线程的参数传递,使用多线程可以同时执行多个任务,提高开发效率,但是在实际开发中往往我们会碰到线程同步问题, ...
- Python多线程中阻塞(join)与锁(Lock)的使用误区
参考资料:https://blog.csdn.net/cd_xuyue/article/details/52052893 1使用两个循环分别处理start和join函数.即可实现并发. threads ...
- 线程高级篇-Lock锁和Condition条件
浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...
- 多线程的异常处理、线程取消、临时变量、lock
异步多线程的异常,抓不到,因为是在子线程执行. #region 多线程的异常处理.线程取消.临时变量.lock { try { List<Task> list = new List< ...
- .NET 异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消
今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...
- .NET异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消
今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型
线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...
- Android多线程研究(9)——线程锁Lock
在前面我们在解决线程同步问题的时候使用了synchronized关键字,今天我们来看看Java 5.0以后提供的线程锁Lock. Lock接口的实现类提供了比使用synchronized关键字更加灵活 ...
随机推荐
- 环境变量PATH、cp命令、mv命令、文档查看cat/more/less/head/tail 各个命令的使用介绍
第2周第2次课(3月27日) 课程内容: 2.10 环境变量PATH2.11 cp命令2.12 mv命令2.13 文档查看cat/more/less/head/tail 2.10 环境变量PATH P ...
- AWK工具 使用介绍
第6周第5次课(4月27日) 课程内容: 9.6/9.7 awk扩展把这里面的所有练习题做一下http://www.apelearn.com/study_v2/chapter14.html 9.6/9 ...
- wpa_supplicant的移植
解压,进入,使用默认配置文件 cd wpa_supplicant-2.6 cp defconfig .config 修改.config文件,修改部分,根据自己的需要进行这部分的配置 #指定libnl的 ...
- C#语法--委托,架构的血液
委托的定义 什么是委托? 委托实际上是一种类型,是一种引用类型. 微软用delegate关键字来声明委托,delegate与int,string,double等关键字一样.都是声明用的. 下面先看下声 ...
- 利用 Flask 动态展示 Pyecharts 图表数据的几种方法
本文将介绍如何在 web 框架 Flask 中使用可视化工具 pyecharts, 看完本教程你将掌握几种动态展示可视化数据的方法,不会的话你来找我呀- Flask 模板渲染 1. 新建一个项目fla ...
- 谁说微服务是Spring Cloud的独角戏?Service Mesh了解一下?
Service Mesh 的概念自 2017 年初提出之后,受到了业界的广泛关注,作为微服务的下一代发展架构在社区迅速发酵,并且孵化出了诸如 Istio 等广受业界关注的面向于云原生 (Cloud N ...
- 配置基于全局地址池的DHCP
配置基于全局地址池的DHCP 配置基于全局地址池的DHCP服务器,从所有接口上的用户都可以选择该地址池中的地址,是个公共地址池. 实验 1.拓扑图 2.实验步骤 基本配置 开启DHCP功能 创建一个全 ...
- 【集合系列】- 深入浅出的分析 Properties
一.摘要 在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.P ...
- JavaScript的函数申明、函数表达式、箭头函数
JavaScript中的函数可以通过几种方式创建,如下. // 函数声明 function getName() { return 'Michael' } // 函数表达式 const getName ...
- luogu P1356 数列的整数性 |动态规划
题目描述 对于任意一个整数数列,我们可以在每两个整数中间任意放一个符号'+'或'-',这样就可以构成一个表达式,也就可以计算出表达式的值.比如,现在有一个整数数列:17,5,-2,-15,那么就可以构 ...