线程
创建线程
1.线程时一个可以独立执行的执行路径
2.在同一个进程中多个线程会共享内存
3.在单核计算机中,操作系统会为每一个线程划分时间片来模拟并发执行
4.在多核心的计算机中,线程是真正的并行的
5.线程是抢占式的,需要和其他代码竞争
6.线程一旦启动,IsAlive属性就会返回true,直至线程停止。线程停止后就无法在启动了
7.设置线程的名称属性在调试时可以在调试栏看到
汇合与休眠
1.Join可以等待线程结束,只有线程结束后才往后面执行,同时可以给join设置超时时间
2.Sleep可以使线程暂停,此时的线程时阻塞的。Sleep(0)可以会使线程立即放弃自己的时间片
阻塞
1.阻塞的线程会立即交出自己的时间片,不再消耗处理器的时间
2.I/O密集:操作的大部分时间都在等待事情发生,一般涉及输入输出
3.计算密集:大量执行CPU操作
锁与线程安全
1.CLR为每一个线程分配了独立的内存栈,保证局部变量的隔离
2.当两个线程同时竞争一个锁时,另外一个线程会等待锁被释放,保证代码的安全性
向线程传递数据
用Lambda表达式
Thread t = new Thread(()=>
{
Go("sss");
});
使用传统方法
Thread t = new Thread(Go);
t.Start("sss");
//此时的m的类型必须是object
static void Go(object m)
{
Console.WriteLine(m);
}
注意:不要在线程中引用同一块内存,应使用局部变量
异常处理
1.使用try/catch包住创建线程的代码是没用的,需要包住线程调用的方法
2.所有线程的入口都要添加异常出路
前台线程和后台线程
1.前台线程只要有一个在运行,则程序依然运行
2.当前台线程结束时,应用程序就停止了,后台线程也随之停止(不管运行否)
3.前台线程尽量等待后台线程的结束
线程的优先级
1.线程和进程都有优先级
2.提高线程的优先级就会减少其他线程的执行时间
3.如果想要提高线程的执行时间还需要提高整个进程的优先级
信号发送
一个线程接受其他线程的信号,以此来决定线程的执行与否
同步上下文
1.可以直接将UI的图形显示出来,但是复杂的数据需要更长时间才能显示
2.采用SynchronizationContext类可以达到次目标,该类可以捕获当前UI线程,在其他的线程中使用该类,可以将其他线程的数据滞后的发送到UI线程中
线程池
1.创建一个线程需要一定的耗时
2.线程池中的线程都是后台线程
3.阻塞线程池中的线程将影响性能
4.Task.Run就是在线程池中创建了一个线程池
5.线程池中线程的数量会根据当前执行的速度由操作系统自动调节
任务
Task不是一个线程,而是表示一个并发操作,而这个操作不一定要用线程来完成
启动任务
1.Task默认使用线程池中的线程,都是后台线程
2.Task.Run()表示热任务,可以直接运行任务,如果需要冷任务,则需要调用Task构造器
3.任务返回的Task类可以追踪当前任务的运行状态
4.Wait可以等待当前线程,同时也可以指定是将和取消令牌来提前取消等待
5.当需要运行长任务时,较好的方法是:
I/O密集型:TaskCompletionSource和异步函数通过回调函数
计算密集型:生产者/消费者队列
返回值
1.使用Func来代替Action就可以使Task有返回值
2.Task.Result可以查看Task的返回值,而该方法会阻塞当前线程
异常
1.如果Task出错了,那么在获取Result时,就会抛出异常,可以通过IsFaulted和IsCanceled来知道异常与否
2.后台程序会产生异常,最好在线程中处理这些异常。
3.有些异常有可能无法观测,有些异常会影响程序的正常运行。用TaskScheduler.UnobersvedTaskException来获取全局的未观测异常,记录日志。
延续
1.延续会告诉任务在完成之后继续执行后续的操作
2.延续通常由一个回调方法实现,该方法会在操作完成之后执行
Task<int> a = Task.Run(() =>
{
Console.WriteLine("sb");
return 1;
});
var awaiter = a.GetAwaiter();
awaiter.OnCompleted(() =>
{
Console.WriteLine(awaiter.GetResult());
});
3.OnCompleted表示在a线程运行完了之后立即执行
4.如果线程抛出异常,则在调用GetResult和Result时,都会重新抛出遗常
5.延续的代码和Task的代码是运行在同一个线程上的
TaskCompletionSource
1.TaskCompletionSource可以创建任务,这个任务是在操作结束或出错时手动创建附属任务
2.真正作用是创建一个不绑定线程的任务
Task.Delay
Task.Delay是Thread.Sleep的异步版本
Task.Delay不会阻塞当前线程,一般会使用await来等待Task.Delay完成
C#的异步函数
等待
1.async await会转化成GetAwaiter和OnCompleted
2.用async修饰会将await作为一个关键字而非标识符来避免二义性
3.async只支持返回类型为Task,Task<TResult>,void三种类型
步骤解析
1.在主线程中,如果没有碰到await,程序就会一直在主线程中。直到碰到await,就会开辟一个线程
2.此时,就有2部分代码,一部分是属于主线程的代码,一部分是分线程的代码。这时,两部分的代码会分别继续执行
3.最为关键的是,要分清楚那一部分为主线程代码,哪一部分为分线程代码。await前面的是主线程,await后面的是分线程。再强调一遍,开启分线程的分水岭是await标志,而不是async标志。也就是在async方法里面,await前面的代码属于主线程,await后面的代码才属于分线程
4.当主线程遇到awiat时,会立即撤出async方法,继续执行主函数代码。
5.分线程会执行剩余的代码。在分线程中调用的其他方法,也都是在分线程中执行的
6.在获取Task.Result时会强行等待
7.2个await就会开启2个线程
优化
1.同步完成,在async函数中不一定要调用await,可以返回同步代码。
2.在循环调用异步方法时,使用ConfigureAwait避免大量的消息回弹
异步模式
取消操作
1.取消令牌,CancellationToken可以取消异步操作
2.CancellationToken可以直接创建一个时间来决定何时取消异步,而不一定要手动执行
进度报告
1.通过向线程中添加一个Action委托来跟踪线程执行情况
2.可以使用IProgress来完成1中的情况
任务组合器
1.WhenAny:会在任务组中的任意一个任务完成时返回这个任务,返回结果是第一个完成的任务。
var a = Task.WhenAny( Fun2(),Fun2());
返回结果为Task<Task<int>>或者Task<int>
可以将异步方法和延迟函数组合从而达到延时的目的.
2.WhenAll:当所有任务都完成时才完成
Task.WhenAll( Fun2(),Fun2());
返回结果是数组任务,即Task<int[]>或者int[]
3.自定义组合器
可以将WhenAny,WhenAll和延时,令牌取消函数组合来形成自定义组合