赞
踩
异步编程是一种思路
异步相当于对线程池的封装
await相当于让另一个线程来干这个事
异步并不意味着多线程,单线程同样可以异步
异步默认借助线程池
多线程经常阻塞,而异步要求不阻塞
适合CPU密集型操作
适合长期运行的任务
线程的创建与销毁开销较大
提供更底层的控制,操作线程、锁、信号量等
线程不易于传参及返回
线程的代码书写较为繁琐
适合IO密集型操作
适合短暂的小任务
避免线程阻塞,提高系统响应能力
Task
)正在运行、完成、结果、报错等
public class TODO { public static event Func<object, string> foo; static void Main() { Task<string> task = new Task<string>((n) => { Thread.Sleep(1500); for (int i = 0; i < (int)n; i++) { Console.WriteLine("DONE {0}", i); } return "ok"; }, 3); Console.WriteLine(task.Status); task.Start(); Console.WriteLine(task.Status); Thread.Sleep(1000); Console.WriteLine(task.Status); Thread.Sleep(2000); Console.WriteLine(task.Status); Console.WriteLine(task.Result); } }
输出结果
Created
WaitingToRun
Running
DONE 0
DONE 1
DONE 2
RanToCompletion
ok
另有ValueTask值类型版本
开启异步任务后,当前线程并不会阻塞,而是可以去做其他事情
异步任务(默认)会借助线程池在其他线程上运行
获取结果后回到之前的状态
返回值为Task的方法表示异步任务没有返回值
返回值为Task<T>则表示有类型为T的返回值
async Task
)将方法标记async
后,可以在方法中使用await
关键字
await
关键字会等待异步任务的结束,并获得结果
async
+ await
会将方法包装成状态机,await
类似于检查点
MoveNext方法会被底层调用,从而切换状态
async Task
返回值依旧是Task类型,但是在其中可以使用await关键字
在其中写返回值可以直接写Task<T> 中的T类型,不用包装成Task<T>
async void
同样是状态机,但缺少记录状态的Task对象
无法聚合异常(Aggregate Exception),需要谨慎处理异常
几乎只用于对于事件的注册
*异步编程具有传染性(Contagious)
*
一处async
,处处async
几乎所有自带方法都提供了异步的版本
await
会暂时释放当前线程,使得该线程可以执行其他工作,而不必阻塞线程直到异步操作完成
不要在异步方法里用任何方式阻塞当前线程
Task.Wait()
& Task.Result
如果任务没有完成,则会阻塞当前线程,容易导致死锁
Task.GetAwaiter().GetResult(),不会将Exception 包装为AggregateException
Task.Delay()
vs. Thread.Sleep()
后者会阻塞当前的线程,这与异步编程的理念不符
前者是一个异步任务,会立刻释放当前的线程
IO
等操作的同步方法
其他繁重且耗时的任务
一种管理和协调线程的机制,允许开发者将代码的执行切换到特定的线程
WinForms
与WPF
拥有同步上下文(UI
线程),而控制台程序默认没有
ConfigureAwait(false)
配置任务通过await方法结束后是否会到原来的线程,默认为true
一般只有UI线程会采用这种策略
TaskScheduler
控制Task
的调度方式和运行线程
线程池线程Default
当前线程CurrentThread
单线程。上下文STAThread
长时间运行线程LongRunning
优先级、上下文、执行状态等
调用一一个异步方法,但是并不使用await或阻塞的方式去等待它的结束
无法观察任务的状态(是否完成、是否报错等)
Task.Run()
Task.Factory.StartNew()
提供更多功能,比如TaskCreationOptions.L ongRunning
Task.Run相当于简化版
new Task
+ Task.Start()
很少有创建一个Task却没有让他立刻开始的
public class TODO { static async Task Main() { Console.WriteLine(12); Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString()); var task = await Task.Run(heavyJob); //Console.WriteLine(task); Console.WriteLine(123); } public static int heavyJob() { Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString()); Thread.Sleep(10); return 1; } }
public class TODO { static async Task Main() { var inputs = Enumerable.Range(10, 10).ToArray(); var tasks = new List<Task<int>>(); Console.WriteLine(Environment.CurrentManagedThreadId); foreach (var input in inputs) { tasks.Add(foo(input)); } await Task.WhenAll(tasks); var outputs = tasks.Select(x => x.Result).ToArray(); foreach (var output in outputs) { Console.WriteLine(output); } } public static async Task<int> foo(int input) { await Task.Delay(5000); return input * 2; } }
CancellationTokenSource
+ CancellationToken
public class TODO { static async Task Main() { var cts = new CancellationTokenSource(); try { var task = Task.Delay(100000, cts.Token); Thread.Sleep(2000); cts.Cancel();//抛出异常 await task; } catch (TaskCanceledException) { Console.WriteLine("ss"); } finally { cts.Cancel(); } } }
OperationCanceledException
& TaskCanceledException
推荐异步方法都带上CancellationToken
这一传参
你自己写了异步方法却不支持传入这个————我可以不用,但不能没有
异步编程不必需要多线程来实现
比如可以在单个线程上使用异步I/O 或事件驱动的编程模型(EAP)
单线程异步:自己定好计时器,到时间之前先去做别的事情
多线程异步:将任务交给不同的线程,并由自己来进行指挥调度
async关键字只是用来配合await 使用,从而将方法包装为状态机
本质上仍然是Task,只不过提供了语法糖,并且函数体中可以直接return Task的泛型类型
接口中无法声明async Task
在使用await关键字调用并等待一个异步任务时,异步方法不一定会立刻来到新的线程上
如果await了一个已经完成的任务(包括Task.Delay(0)),会直接获得结果
异步编程与多线程有一定关系,但两者并不是可以完全互相替代
如果任务已经完成,那么Task.Result 可以直接得到结果
await关键字不一定会立刻释放当前线程,所以如果调用的异步方法中存在阻塞(如Thread.Sleep(O))
那么依旧会阻塞当前上下文对应的线程
Monitor(lock)
Mutex
Semaphore
EventWaitHandle
所有只有SemaphoreSlim不阻塞
ManualResetEventSlim
AsyncManulResetEvent
Miccrosoft.VisualStudio.Threading
AsyncLock
Nito.AsyncEx
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。