一、异常处理
1、下面for循环20个线程,到11,12号的时候执行失败,这里我也用了try catch来捕获异常。
cs
private void button11_Click(object sender, EventArgs e)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
try
{
for (int i = 0; i < 20; i++)
{
string name = string.Format($"Click_{i}");
Action<object> act = t =>
{
Thread.Sleep(2000);
if (t.ToString().Equals($"Click_11"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
if (t.ToString().Equals($"Click_12"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
Console.WriteLine("{0} 执行成功", t);
};
taskList.Add(taskFactory.StartNew(act, name));
}
}
catch (AggregateException aex)
{
foreach (var item in aex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
打印出来发现并没有捕获到异常
那么我再新增一句:
cs
Task.WaitAll(taskList.ToArray());
这样我们就可以成功捕获到异常了。 同时,我们也可以通过AggregateException,捕获到我们异常的数据。
最开始我们抓不到异常,是因为系统跑出了try catch,我们抓不到。
而WaitAll可以抓到多线程里面的所有异常
但是产生了一个新的问题,WaitAll会卡界面
线程里面的action不允许出现异常,需要自己处理好
二、线程取消
多个线程并发,某个失败后,希望别的线程停下来。
task外部无法中止,Thread.Abort不靠谱,因为线程是OS的资源,无法掌控啥时候取消
线程自己停止自己------公共的访问变量------修改它------线程不断的检测它(会有延迟)
CancellationTokenSource标志任务是否取消 Cancel 表示取消 IsCancellationRequested表示是否取消。
Token启动Task的时候传入,那么如果Cancel 了,这个任务就会放弃启动,抛出一个异常
cs
private void button12_Click(object sender, EventArgs e)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
CancellationTokenSource cts = new CancellationTokenSource(); //bool值
for (int i = 0; i < 20; i++)
{
string name = $"Click_{i}";
Action<object> act = t =>
{
try
{
Thread.Sleep(2000);
if (t.ToString().Equals($"Click_11"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
if (t.ToString().Equals($"Click_12"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
//除了11和12抛异常,其他的我们检查一下。
//如果已经取消了,那么我们放弃执行
if (cts.IsCancellationRequested) //检查信号量
{
Console.WriteLine($"{t} 放弃执行");
return;
}
//如果还没取消了,那么我们正确执行
else
{
Console.WriteLine($"{t} 执行成功");
}
}
catch (Exception ex)
{
cts.Cancel();
Console.WriteLine(ex.Message);
}
};
taskList.Add(taskFactory.StartNew(act, name, cts.Token));
}
Task.WaitAll(taskList.ToArray());
}
三、多线程的临时变量
1、闭包问题
cs
private void button13_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
int k = i;
Task.Run(() =>
{
Thread.Sleep(100);
Console.WriteLine(k);
});
}
}
四、多线程的线程安全问题
抛出问题:
cs
private void button14_Click(object sender, EventArgs e)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
int totalCount = 0;
List<int> intList = new List<int>();
for (int i = 0; i < 10000; i++)
{
int newi = i;
taskList.Add(taskFactory.StartNew(() =>
{
totalCount += 1;
intList.Add(newi);
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(totalCount);
Console.WriteLine(intList.Count);
}
这里我声明了一个int类型的临时变量和一个List<int>类型的临时变量,让他们嵌套在多线程内进行累加。
可以发现打印出来的数目并不一致,并且都小于10000?
这就是线程安全的问题,是多个线程同时操作同一变量导致的。
对于共有变量:都能访问的局部变量/全局变量/数据库的值/硬盘文件
我们尝试加上锁(Lock)(Lock可以算是一种语法糖)。
cs
private static readonly object btnThreadCore_Click_Lock = new object();
private void button14_Click(object sender, EventArgs e)
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
int totalCount = 0;
List<int> intList = new List<int>();
for (int i = 0; i < 10000; i++)
{
int newi = i;
taskList.Add(taskFactory.StartNew(() =>
{
lock (btnThreadCore_Click_Lock)
{
totalCount += 1;
intList.Add(newi);
}
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(totalCount);
Console.WriteLine(intList.Count);
}
发现打印出来的数量都正常。
因为lock后的方法块,任意时刻只有一个线程可以进入
1、Lock介绍:
介绍:Lock 等同于Monitor.Enter(btnThreadCore_Click_Lock);
离开等同于调用了Monitor.Exit();
限制:Lock只能锁引用类型(占用引用链接),不要用string,因为享元
微软提供的标准写法:
private static readonly object btnThreadCore_Click_Lock = new object();
Lock 最好锁private的,防止外面也去lock
static 全场唯一 ,避免不同实例锁的不同
readonly 只读,不要改动
object 表示引用
lock(this)每次实例化都是不同的锁,同一个实例时相同的锁但是这个实例别人也能访问到,别人也能锁定
缺点:Lock解决,因为只有一个线程可以进去,没有并发,所以解决了问题,但是牺牲了性能,所以要尽量缩小lock的范围
2、安全队列(ConcurrentQueue):
最后还是需要一个线程完成操作
3、最好的方法就是不要冲突------数据拆分,避免冲突!!