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关键字更加灵活 ...
随机推荐
- Chapter 03—Getting Started with graphs
例01:一个简单的例子 一. 图形参数 1. 符号和线条 例02: plot(dose,drugA,type="b",lty=3,lwd=3,pch=1 ...
- python文件高级操作
python文件高级操作和注意事项等等 文件过大保护 由于read是一次性读取文件所有的内容,如果文件100G,内存就会吃不消,所以推荐使用read(size)一次读取指定字节/字符(根据rb,或者r ...
- mysql主从架构搭建
1.配置文件,开启二进制日志 vim /etc/my.cnf 在mysql下增加如下内容 server-id= log-bin=mysql-bin relay-log=mysql-relay 2.登录 ...
- 引用公共页面的js函数报错
对于网站来说很多页面之间都有着大量的共享模块,如页头,页脚和用户栏等.很多时候为了方便.省事,我们在公共模块写函数,然后在别的页面里调用.但我们在引用公共的js函数时,有些可以引用,有些却报错:这是因 ...
- ruby2.2 DevKit 安装后无法使用解决方案
windows 系统下,Ruby 的某些 gem 包需要 DevKit 才能正常安装,2.4 以后的版本可以一键安装 DevKit,之前的版本只能手动安装. 2.4 以后的可以到官网下载:https: ...
- 华为“方舟编译器”到底是啥?一文看懂TA如何让手机性能再突破【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- Kubernetes增强型调度器Volcano算法分析
[摘要] Volcano 是基于 Kubernetes 的批处理系统,源自于华为云开源出来的.Volcano 方便 AI.大数据.基因.渲染等诸多行业通用计算框架接入,提供高性能任务调度引擎,高性能异 ...
- 转:解决Eclipse中.properties文件中文乱码问题
在.properties文件写注释时,发现中文乱码了,由于之前在idea中有见设置.properties文件的编码类型,便找了找乱码原因 在中文操作系统中,Eclipse中的Java类型文件的编码的默 ...
- apache thrift 和 apache jersey 记录
几篇好的入门文档链接: 1. Hello World by Thrift Using Java 2. Thrift 实例 Helloworld 3. Thrift版的Hello World 4. Th ...
- lodash.memoize
目录 _.memoize(func, [resolver]) 举例1: 获取J(1000000)的值 举例2: 斐波那契数列F(1000)的值 _.memoize(func, [resolver]) ...