笔记整理(原资源网址): https://www.bilibili.com/video/BV1Zf4y117fs?p=12
线程(Thread)是用来创建并发(concurrent)的一种低级别工具,它有一些限制,尤其是:
虽然开始线程的时候可以方便的传入数据,但是当 Join 的时候,很难从线程获得返回值。
可能需要设置一些共享字段。
如果操作抛出异常,捕获和传播该异常都很麻烦。
无法告诉线程在结束时开始做另外的工作,你必须进行 Join 操作(在进程中阻塞当前的线程)
很难使用较小的并发来组建大型的并发,导致了对手动同步的更大依赖以及随之而来的问题
Task 类可以很好的解决上述问题
Task 是一个相对高级的抽象:它代表了一个并发操作(concurrent)
该操作可能由 Thread 支持,或不由 Thread 支持
Task 是可组合的(可使用 Continuation 把它们串成链)
Task 可以使用线程池来减少启动延迟
使用 TaskCompletionSource,Task 可以利用回调的方式,在等待 I/O-bound 操作时完全避免使用线程。
Task 类在 System.Threading.Tasks 命名空间下。
开始一个 Task 最简单的方法是使用 Task.Run,传入一个 Action 委托
Task 默认使用线程池,也就是后台线程:当主线程结束时。你创建的所有 tasks 都会结束。
Task.Run 返回一个 Task 对象,可以使用它来监视其过程
在Task.Run之后,没有调用 Start 方法,因为 Run 方法创建的是“热”任务,可以通过 Task 的构造函数创建“冷”任务,但是很少这样做。
可以通过Task 的 Status 属性来跟踪 Task 的执行状态。
调用 Task 的 Wait 方法会进行阻塞直到操作完成,相当于调用 Thread 上的 Join 方法。
Wait 也可以让你指定一个超时时间和一个取消令牌来提前结束等待。
默认情况下,CLR在线程池中运行 Task,这非常适合短时间运行的 Compute-Bound 类工作。
针对长时间运行的任务或阻塞操作,可以不采用线程池。
如果同时运行多个 Long-running Task (尤其是其中有处于阻塞状态的),那么性能将会受很大影响,这时有比 TaskCreationOption.LongRunning 更好的办法:
如果任务是 IO-Bound,TaskCompletionSource 和异步函数可以让你用回调(Coninuations)代替线程来实现并发。
如果任务是 Compute-Bound,生产者/消费者队列允许你对任务的并发性进行限流,避免把其他线程和进程饿死。
Task 有一个泛型子类 Task<TResult>,它允许发出一个返回值。
使用 Func<TResult> 委托或兼容的 Lambda 表达式来调用 Task.Run 就可以得到 Task<TResult>。
随后可以通过 Result 属性来获得返回值,如果这个 Task 还没有完成操作,访问 Result 属性会阻塞该线程直到该 Task 完成操作。
Task<TResult> 可以看作是一种所谓的“未来/许诺”(future、promise),在它里面包裹着一个 Result,在稍后的时候就会变得可用。