多线程的异常处理示例
线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作
如果某一个线程异常了,需要通知或终止其他线程示例
//多线程并发任务,某个失败后,希望通知别的线程,都停下来,how?
//Thread.Abort--终止线程;向当前线程抛一个异常然后终结任务;线程属于OS资源,可能不会立即停下来
//Task不能外部终止任务,只能自己终止自己(上帝才能打败自己)
//cts有个bool属性IsCancellationRequested 初始化是false
//调用Cancel方法后变成true(不能再变回去),可以重复cancel
try
{
CancellationTokenSource cts = new CancellationTokenSource();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 50; i++)
{
string name = $"btnThreadCore_Click_{i}";
taskList.Add(Task.Run(() =>
{
try
{
//判断cts.IsCancellationRequested,如果其他线程已经出现异常了,则终止线程内的任务
if (!cts.IsCancellationRequested)
Console.WriteLine($"This is {name} 开始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Thread.Sleep(new Random().Next(50, 100));
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_13"))
{
cts.Cancel();
}
//可以在多个核心位置进行判断
if (!cts.IsCancellationRequested)
{
Console.WriteLine($"This is {name}成功结束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
Console.WriteLine($"This is {name}中途停止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//捕获到其中一个线程异常,则调用cts.Cancel()
cts.Cancel();
}
//也可以在Task.run的参数里传递cts.Token,当出现异常后即不再启动(创建)后续的线程
}, cts.Token));
}
//1 准备cts 2 try-catch-cancel 3 Action要随时判断IsCancellationRequested
//尽快停止,肯定有延迟,在判断环节才会结束
Task.WaitAll(taskList.ToArray());
//如果线程还没启动,能不能就别启动了?
//1 启动线程传递Token 2 异常抓取
//在Cancel时还没有启动的任务,就不启动了;也是抛异常,cts.Token.ThrowIfCancellationRequested
}
//AggregateException是专门捕获线程异常的
catch (AggregateException aex)
{
foreach (var exception in aex.InnerExceptions)
{
Console.WriteLine(exception.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
临时变量
#region 临时变量
{
//for (int i = 0; i < 5; i++)
//{
// Task.Run(() =>
// {
// 最终i显示都是5
// Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
// });
//}
//处理方法
//临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候,i已经是5了
//k是闭包里面的变量,每次循环都有一个独立的k
//5个k变量 1个i变量
for (int i = 0; i < 5; i++)
{
int k = i;
Task.Run(() =>
{
Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
}
}
#endregion
Lock
Lock锁是排斥其他线程!!
推荐使用的lock锁变量
private static readonly object Form_Lock = new object();
线程安全:如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的
线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改
发生是因为多个线程相同操作,出现了覆盖,怎么解决?
1 Lock解决多线程冲突
Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object,
A不能是Null,可以编译不能运行;
B 不推荐lock(this),外面如果也要用实例,就冲突了
C 不应该是string; string在内存分配上是重用的,会冲突
D Lock里面的代码不要太多,这里是单线程的
线程安全问题
1.使用官方的线程安全集合,在改集合内进行多线程操作
// 线程安全集合
System.Collections.Concurrent.ConcurrentQueue<int>
2.数据分拆,避免多线程操作同一个数据;又安全又高效,这才是最顶的