进程/线程
- 进程: 记录一个应用程序在运行的过程中,消耗的计算机资源的总记录; 计算机的资源:CPU/磁盘IO/网络
- 线程:在程序运行的过程中,对于任何一条指令执行的一个最小执行流;
- 进程和线程:一个进程至少要包含一个线程;
- 多线程:程序在运行的过程中,可以多个线程同时去执行;
- 重点: 进程,还是线程----都属于是计算机的资源;
启动线程的多种方式
直接New
Task task = new Task(() =>
{
Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});
Task.Run
Task.Run(() =>
{
Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});
TaskFactory
TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() =>
{
Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});
Task.Factory.StartNew
Task.Factory.StartNew(() =>
{
Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});
线程等待
Thread.Sleep 和 Task.Delay的区别
- Thread.Sleep:
等待主线程 直接卡顿界面 影响用户的体验
Thread.Sleep(1000);
- Task.Delay:
不会等待主线程 一般伴随 ContinueWith 一起使用
表示等待多长时间之后 可以执行一个回调方法
Task.Delay(1000);
=================
Task.Delay(1000).ContinueWith(task =>
{
});
Task.WaitAll
使用场景:
如果希望所有的线程都执行结束后,再执行一段业务逻辑
特点:
等待(等待主线程/UI线程)所有线程执行完 才执行以下业务 会卡顿界面
Task.WaitAll(tasks.ToArray());
Task.WaitAny
使用场景:
如果希望这一堆任务中,其中只要有一个任务执行完毕就继续往后执行
特点:
等待(等待主线程/UI线程)任一一个线程执行完 才执行以下业务 会卡顿界面
Task.WaitAny(tasks.ToArray());
Task.Factory.ContinueWhenAll
使用场景:
既等待也不卡界面的等待方式 等待一堆线程执行完之后 继续往下执行
特点:
等待一堆线程执行完之后 继续往下执行
第二个参数是执行完的Task集合
Task.Factory.ContinueWhenAll(tasks.ToArray(), taskList =>
{
Console.WriteLine("不卡顿界面所有线程执行之后继续往后执行!!!");
});
Task.Factory.ContinueWhenAny
使用场景:
既等待也不卡界面的等待方式 等待一堆线程中,任何一个线程执行完之后 继续往下执行
特点:
等待一堆线程中,任何一个线程执行完之后 继续往下执行
第二个参数是执行完的Task
Task.AsyncState:执行完的状态对象,可以在创建的时候传值
Task.Factory.ContinueWhenAny(tasks.ToArray(), task =>
{
Console.WriteLine($"{task.AsyncState}执行结束:不卡顿界面任何一个线程执行完之后继续往后执行!!!");
});
Task.AsyncState传值Demo:
必须使用:Task.Factory.StartNew,创建一个带有Object的Action对象
List<Task> tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
int k = i;
tasks.Add(Task.Factory.StartNew(o =>
{
Console.WriteLine($"开始逻辑:{k}");
Thread.Sleep(200);
Console.WriteLine($"结束逻辑:{k}");
}, $"任务{k}"));
}
执行如下:
多线程异常处理
- 在多线程的内部发生的异常 外部的 try-catch 是捕捉不到的
- 如果把 try-catch 写在task内部可以捕捉到,不过是单个线程的捕捉
- 捕捉异常:线程等待 Task.WaitAll
- 捕捉到的多线程异常,有什么特点:AggregateException:类型的异常
- 一个 try 可以对应多个 catch,异常类型的匹配优先具体类型,如果没有再匹配抽象
Demo代码:
try
{
List<Task> taskList = new List<Task>();
for (int i = 0; i < 20; i++)
{
int k = i;
taskList.Add(Task.Run(() =>
{
if (k == 6)
throw new Exception("k == 6");
if (k == 9)
throw new Exception("k == 9");
if (k == 11)
throw new Exception("k == 11");
}));
}
Task.WaitAll(taskList.ToArray());
}
// 如果多个catch 优先匹配具体类型 如果没有才会去匹配抽象
catch (AggregateException ex)
{
foreach (var item in ex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
线程取消
- 线程不能从外部取消,线程只能自己取消自己
- 来一个信号量:每个线程去判断这个信号量,如果有线程发生异常了,就去修改这个信号量
- 线程取消的标准方案:CancellationTokenSource,提供IsCancellationRequested属性,默认值为false
- 同时提供Cancel方法, IsCancellationRequested: false --> true
Demo代码:
CancellationTokenSource cts = new CancellationTokenSource();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 200; i++)
{
int k = i;
taskList.Add(Task.Run(() =>
{
// 线程取消 只能取消还没有判断的线程
if (cts.IsCancellationRequested)
{
Console.WriteLine($"开始:{Thread.CurrentThread.ManagedThreadId.ToString("000")}");
}
else
{
Console.WriteLine($"Cancel结束:{Thread.CurrentThread.ManagedThreadId.ToString("000")}");
cts.Cancel();
}
if (k == 6)
cts.Cancel();
},cts.Token));
}
临时变量
- 多线程:延时启动不会阻塞主线程
- 因为task启动的时候需要请求计算机需要内存然后去启动,这个时候for循环如果很少的话,就会执行完毕,导致i为最大的++
- 如果需要一个中间变量去接受i,然后输出,必须在循环里面:因为每次循环都是新的变量,如果把变量定义在for循坏外面则和输出i一样。
for (int i = 0; i < 20; i++)
{
int k = i;
Task.Run(() =>
{
Console.WriteLine(k);
});
}
线程安全
多线程和单线程执行逻辑一致就是线程安全
- 加锁:标准锁:锁静态对象
-
private static readonly object obj_lock = new object();
- 使用线程安全集合:
-
ConcurrentDictionary<>
- 分片执行:
-
- 出现线程安全的主要场景,多个线程同时去操作变量、同时操作计算机文件
- 避开让多个线程有机会同时去操作同一块数据
加锁案例:
private static readonly object obj_lock = new object();
List<Task> taskList = new List<Task>();
int AsyncNum = 0;
for (int i = 0; i < 1000_0; i++)
{
taskList.Add(Task.Run(() =>
{
lock (obj_lock)
{
AsyncNum++;
}
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(AsyncNum);