System.Threading.Task中的Parallel.For和Parallel.Foreach是真正的的多核处理器并行执行程序。
并行与并发的区别
private void ParallelKnoledgePoints()
{
{
//最简洁的将串行的代码并行化,并行线程
Parallel.Invoke(() => this.DoSomethingLong($"btnParallelClick Invoke this ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}"),
() => this.DoSomethingLong($"btnParallelClick Invoke this ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}"),
() => this.DoSomethingLong($"btnParallelClick Invoke this ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}"),
() => this.DoSomethingLong($"btnParallelClick Invoke this ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}"),
() => this.DoSomethingLong($"btnParallelClick Invoke this ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
}
{
//Paraller.for它会在底层根据硬件线程的运行状况来充分的使用所有的可利用的硬件线程
//注意这里的Parallel.for的步行是1
Parallel.For(0, 5, i => this.DoSomethingLong($"btnPallelClick For {i}"));
}
{
//forEach的独到之处就是可以将数据进行分区,每一个小区内实现串行计算,分区采用Partitioner.Create实现。
//Partitioner是分区器
int[] intArray = new int[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(intArray, i => this.DoSomethingLong($"btnPallelClick Foreach {i}"));
}
}
///
/// 控制线程数量
/// 这种会卡顿界面
///
private void ControlTheNumberOfThreeads01()
{
//如何控制线程数量
//如果需要异步执行大量的数据,肯定希望多线程执行,想要控制线程数量
//1.ParalleOptions
//2.设置MaxDegreeOfParallelism(设置最多开启多少个线程)
//3.将Options传入Parallel.For
//执行结果分析,之所以有时候结束了两个就又开启了一个
//是因为只要保证总的开启的线程数在3个以内就可以
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallelClick_{i}"));
}
///
/// 控制线程数量
/// 不卡顿界面
///
/// 工作中用到Parallel其实更多的就是控制线程数量
///
private void ControlTheNumberOfThreads02()
{
//如何让主线程不参与?——包一层
//上面一种方法因为主线程参与运算了,所以才会卡顿界面
//如何不卡顿界面?
//开启一个子线程
//结果分析:发现开启的线程中没有了01线程,而且不再卡顿界面
Task.Run(() =>
{
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallelClick_{i}"));
});
}
///
/// 多线程异常处理
/// 在子线程内部发生异常,异常找不到了,那么异常去哪了?必然是被吞掉了
/// 1.如何把异常捕捉到呢?
/// a.list存入所有的Task
/// b.Task.WaitAll()可以捕捉到AggregateException类型异常
/// c.可以多个Catch来捕捉异常,异常——先具体再全部
/// d.可以通过aexception.InnerExceptions获取到多线程中所有的异常
///
/// 2.多线程中发生异常是个很尴尬的事情,如果多个线程同时执行业务,如果有一个线程异常了
/// 其实对整个业务链来说是不完美的,需要一些应对策略,可能就需要让其他的线程停止下来,重新操作
///
private void MultithreadedExceptionHandling()
{
//Thread thread = null;
//thread.Abort();//可以停止线程
//在调用System.Threading.ThreadAbortException的线程中
//引发System.Threading.ThreadAbortException,以开始终止线程的过程,调用此方法通常会终止线程
try
{
for(int i = 0; i < 10; i++)
{
string name = $"MultithreadedExceptionHandling_{i}";
Task.Run(() =>
{
if (name.Equals("MultithreadedExceptionHandling_8"))
{
throw new Exception("捕捉不到 MultithreadedExceptionHandling_8异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_3"))
{
throw new Exception("捕捉不到 MultithreadedExceptionHandling_3异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_5"))
{
throw new Exception("捕捉不到 MultithreadedExceptionHandling_5异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_1"))
{
throw new Exception("捕捉不到 MultithreadedExceptionHandling_1异常了……");
}
Console.WriteLine($"正常值 看i值,没一个是异常值8、3、5、1 this is {name}成功,ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
}
}
catch(AggregateException aex)
{
//通过异常的InnerExceptions获取到多线程中所有的异常
foreach(var aexception in aex.InnerExceptions)
{
Console.WriteLine(aexception.Message);
}
Console.WriteLine(aex.Message);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
try
{
List<Task> list = new List<Task>();
for (int i = 0; i < 10; i++)
{
string name = $"MultithreadedExceptionHandling_{i}";
list.Add(Task.Run(() =>
{
if (name.Equals("MultithreadedExceptionHandling_8"))
{
throw new Exception("抓到了 MultithreadedExceptionHandling_8异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_3"))
{
throw new Exception("抓到了 MultithreadedExceptionHandling_3异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_5"))
{
throw new Exception("抓到了 MultithreadedExceptionHandling_5异常了……");
}
else if (name.Equals("MultithreadedExceptionHandling_1"))
{
throw new Exception("抓到了 MultithreadedExceptionHandling_1异常了……");
}
Console.WriteLine($"正常值 this is {name}成功,ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}));
}
Task.WaitAll(list.ToArray());
}
catch (AggregateException aex)
{
//通过异常的InnerExceptions获取到多线程中所有的异常
foreach (var aexception in aex.InnerExceptions)
{
Console.WriteLine(aexception.Message);
}
Console.WriteLine(aex.Message);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
///
/// 多线程中,如果有一个执行失败,需要取消其他的线程
/// 多个线程并行时,有一个线程发生异常,此时需要通知别的线程停止执行,该怎么做?
///
/// 采用全局变量的方法取消线程
/// 这也是一种解决方案,但是简单,不标准
///
private void GlobalVarThreadCancellation()
{
bool isOk = true;
for(int i = 0; i < 10; i++)
{
string name = $"GlobalVarThreadCancellation_{i}";
Task.Run(() =>
{
if (isOk)
{
Console.WriteLine($"{name} start…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
throw new AggregateException();//这样也可以让线程停下来
}
if (name.Equals("GlobalVarThreadCancellation_8"))
{
isOk = false;
throw new Exception($"GlobalVarThreadCancellation_8异常了…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else if (name.Equals("GlobalVarThreadCancellation_13"))
{
throw new Exception($"GlobalVarThreadCancellation_13异常了…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else if (name.Equals("GlobalVarThreadCancellation_25"))
{
throw new Exception($"GlobalVarThreadCancellation_25异常了…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else if (name.Equals("GlobalVarThreadCancellation_41"))
{
throw new Exception($"GlobalVarThreadCancellation_41异常了…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
//当出现某个异常后,isOk设为false,这时别的线程可能还会执行,我们不管它们
//但是,我们通过isOk不让它们结束
if (isOk)
{
Console.WriteLine($"{name} End…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
//这里也可以回滚
throw new AggregateException();
}
});
}
}
///
/// 线程取消标准做法
/// 跟前边的全局变量法有异曲同工之妙
/// 表面上看跟全局变量有点像,但是不等价,CancellationTokenSource是线程安全的
/// Cancel()方法调用以后,IsCancellationRequested指定为true,是不能再重置回来的
/// 以上的方法再根据实际需求加上业务判断,实现线程的取消
/// 如果在Cancel之前已经进入业务处理的线程是无法停止下来的,所以在最后再判断一次
/// 不让你正常结束
///
private void StandardPracticeThreadCancellation()
{
//CancellationTokenSource
//有一个属性:IsCancellationRequested,默认值为false
//还有一个Cancel方法,只要是Cancel()执行,就可以把IsCancellationRequested指定为true
//可以重复调用Cancel()方法
CancellationTokenSource cts = new CancellationTokenSource();
for(int i = 0; i < 10; i++)
{
string name = $"StandardPracticeThreadCancellation_{i}";
Task.Run(() =>
{
//只要是执行到这里了,就是一个单独的线程
if (!cts.IsCancellationRequested)//IsCancellationRequested属性默认值为false
{
Console.WriteLine($"{name} Start…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
//可以事务回滚
//throw new AggregateException();
Console.WriteLine($"{name} Start…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
{
//这里可以自定义业务逻辑去执行,无论执行多少
Console.WriteLine("自定义业务逻辑……");
}
if (name.Equals("StandardPracticeThreadCancellation_8"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_8异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_3"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_3异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_5"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_5异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_1"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_1异常了……");
}
//如果有业务需要,比如业务逻辑错误,其实也可以直接Cancel(),其他的线程也都停止下来了
if (!cts.IsCancellationRequested)
{
Console.WriteLine($"{name} End…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
//这里也可以回滚
//throw new AggregateException();
//执行业务逻辑的时候,第一个线程取消了,但是第二个小线程在这个线程取消的同时正在执行上边的业务逻辑
//那么我们就让它执行,在上边判断需要线程取消的时候,这里不让第二个线程执行的逻辑结果上传数据库就可以了
Console.WriteLine($"{name} 失败了 End…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
});
}
}
///
/// 比如说有10个线程,其中有一个线程启动了,其它别的线程还没启动
/// 刚好就这个启动的线程异常了,还没启动的那9个线程还有必要启动吗?没必要
/// 这时候只需要把CancellationTokenSource.Token(返回的是一个CancellationToken对象)给Task.Run()即可
///
private void StandardPraticeThreadCancellation2()
{
CancellationTokenSource cts = new CancellationTokenSource();
for(int i = 0; i < 10; i++)
{
string name = $"StandardPracticeThreadCancellation_{i}";
//如何知道线程被取消了呢?
List<Task> list = new List<Task>();
Thread.Sleep(new Random().Next(100, 300));
try
{
list.Add(Task.Run(() =>
{
//只要是执行到这里了,就是一个单独的线程
if (!cts.IsCancellationRequested)
{
Console.WriteLine($"{name} Start…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
//可以事务回滚
//throw new AggregateException();
Console.WriteLine($"{name} 失败了 ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
{
//这里可以自定义业务逻辑去执行,无论执行多少
Console.WriteLine("自定义业务逻辑……");
}
if (name.Equals("StandardPracticeThreadCancellation_8"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_8异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_13"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_13异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_25"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_25异常了……");
}
else if (name.Equals("StandardPracticeThreadCancellation_41"))
{
cts.Cancel();//就可以把IsCancellationRequested指定为true
throw new Exception("StandardPracticeThreadCancellation_41异常了……");
}
//如果有业务需要,比如业务逻辑错误,其实也可以直接Cancel(),其他的线程也都停止下来了
if (!cts.IsCancellationRequested)
{
Console.WriteLine($"{name} End…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
//这里也可以回滚
//throw new AggregateException()
//执行业务逻辑的时候,第一个线程取消了,但是第二个线程在这个线程取消的同时正在执行上面的逻辑
//那么我们就让它执行,在上边判断需要线程取消的时候,这里不让第二个线程执行的逻辑结果上传数据库就可以了
Console.WriteLine($"{name} 失败了 End…… ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
}, cts.Token));//只需要把cts.Token给Task.Run(),即可实现上述场景
}
catch(AggregateException aex)
{
foreach(var ex in aex.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
}
///
/// 临时变量
/// 1、线程的开启是非阻塞的,延迟启动
/// 2、这里是循环5次,代码执行很快
/// 3、每次循环都定义了一个k,就是在作用域以内定义的变量——闭包,只是对当前作用域内的代码生效
///
private void TemporaryVariable()
{
for(int i = 0; i < 1000; i++)
{
int k = i;
Task.Run(() =>
{
Console.WriteLine($"ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")} i = {i} k = {k}");
});
}
}
///
/// 线程安全问题
/// 如果说你的代码在单线程情况下执行的结果和多线程执行的结果完全一致,那么这就是线程安全的
/// 多个线程对同一个栈内存中的数据进行操作时,可能会把上一个现成的结果覆盖掉
/// 线程安全问题一般发生在全局变量、共享变量、硬盘文件,只要是多线程都能访问和修改的公共数据
///
/// 线程安全问题怎么解决呢?
/// 1.lock 可以,但是极力不推荐,因为lock是反多线程的
/// 2.wait 不行
/// 3.线程休眠 不靠谱
/// 4.延迟 不行
///
/// 一、推荐使用的方法:框架提供的,可以放心使用
/// 1.引入命名空间System.Collections.Concurrent,这是线程安全数据结构
/// 2.把之前的非线程安全的数据结构更换成以下数据结构即可:
/// BlockingCollection为实现IProducerConsumerCollection的线程安全集合提供阻塞和限制功能
/// ConcurrentBag表示对象的的线程安全的无序集合
/// ConcurrentDictionary表示可由多个线程同时访问的键值对的线程安全集合
/// ConcurrentQueue表示线程安全的先进先出(FIFO)集合即线程安全的队列
/// ConcurrentStack表示线程安全的后进先出(LIFO)集合即线程安全的栈
/// OrderablePartitioner表示讲一个可排序数据源拆分成多个分区的特定方式
/// Partitioner提供针对数组、列表和可枚举项的常见分区策略
/// Partitioner表示将一个数据源拆分成多个分区的特定方式
///
/// 二、Lock锁,排他性,独占,反多线程的,不推荐
///
/// 三、线程安全问题一般发生在全局变量、共享变量、硬盘文件等,只要是多线程都能访问和修改的公共数据
/// 既然多线程去操作会有线程安全问题,那么就拆分数据源,然后每一个线程对标于单独的某一个数据块
/// 多线程操作完毕以后,再合并数据,爬虫实战会有应用
///
private int SyncNum = 0;
private int ASyncNum = 0;
//使用lock的话,这里一定使用静态变量,而且一定要是object对象
//因为static不会被回收,readonly只能读不能改
private static readonly object obj_Form = new object();
private void ThreadSafety()
{
for(int i = 0; i < 10000; i++)
{
this.SyncNum++;
}
List<Task> taskList = new List<Task>();
for(int i = 0; i < 10000; i++)
{
taskList.Add(Task.Run(() =>
{
this.ASyncNum++;
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine($"SyncNum = {this.SyncNum} ASyncNum = {this.ASyncNum}");
//执行结果:
//this.SycnNum = 10000;
//this.ASyncNum = 9988;
//而且操作多次发现this.SyncNum的值是相同的,但是this.ASyncNum的值不同,而且小于1000
//为什么?因为线程安全问题
//多个线程对同一个栈内存中的数据进行操作时,可能会把上一个线程的结果覆盖掉
List<int> list = new List<int>();
BlockingCollection<int> blockingCollection = new BlockingCollection<int>();
ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
ConcurrentDictionary<string, int> concurrentDictionary = new ConcurrentDictionary<string, int>();
ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();
ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
//OrderablePartitioner orderablePartitioner = new OrderablePartitioner();
for(int i = 0; i < 10000; i++)
{
int k = i;
Task.Run(() =>
{
list.Add(k);
blockingCollection.Add(k);
concurrentBag.Add(k);
concurrentDictionary.TryAdd($"concurrentDictionary_{k}", k);
concurrentQueue.Enqueue(k);
concurrentStack.Push(k);
});
}
Console.WriteLine($"listCount = {list.Count()}");
Console.WriteLine($"blockingCollectionCount = {blockingCollection.Count()} " +
$"concurrentBagCount = {concurrentBag.Count()} " +
$"concurrentDictionaryCount = {concurrentDictionary.Count()} " +
$"concurrentQueueCount = {concurrentQueue.Count()} " +
$"concurrentStackCount = {concurrentStack.Count()}");
//标准锁,锁对象,引用类型但是不要锁string,可能会冲突
//强烈建议,以后使用lock的话,按照标准格式private static readonly object obj_Form = new object();
List<int> lockList = new List<int>();
for(int i = 0; i < 10000; i++)
{
int k = i;
Task.Run(() =>
{
lock (obj_Form)//只允许一个线程从这里经过,这不就是单线程了吗?lock其实是反多线程的
{
lockList.Add(k);
}
});
}
Console.WriteLine($"lockListCount = {lockList.Count()}");
}