C#学习笔记 -- 异步
0、异步方法
对于 C# 中的 async 和 await,可以这么简单理解:
async 告诉 runtime,这个函数可以异步去执行以提高效率。
await 则告诉 runtime,真正耗时的是在我这个关键字后面的操作。
不干等,有情况再叫 runtime 过来!哪怕多跑几趟
await Task.Run()ConfigureAwait(bool)
bool == true
当被await修饰的方法执行完毕, 会检查当前runtime线程有没有在干活, 如果干活就等待干完, 接着runtime往下执行
bool == false
当被await修饰的方法执行完毕, 随机拿个线程继续向下执行, 效率高
-
用
async
修饰的方法 -
返回值一般是
Task<T>
-
方法名字通常以
Async
结尾 -
即使方法没有返回值, 也最好把返回值声明为, 非泛型的
Task
-
调用方法时, 一般在方法前加上
await
关键字, 这样取得的返回值是泛型指定的T
类型 -
异步方法具有传染性, 一个方法如果有
await
调用, 则这个方法的修饰符必须为async
static async Task Test() { string str = await GetTextAsync(); } static async Task<string> GetTextAsync() { return ... }
-
类库中如果同步异步方法都存在, 尽量调用异步方法来提高系统并发吞吐量
-
对于不支持的异步方法, 调用
Wait()
,Result()
, 容易造成死锁的风险
1、异步委托
-
如果在Lambda表达式中需要调用异步方法, 请在Lambda表达式最前方用
async
修饰
public void TestAsyncDelegate() { ThreadPool.QueueUserWorkItem(async o => { while (true) { await File.WriteAllTextAsync(@"F:\yzk\a", "aaa"); } }); }
2、异步原理
Thread.CurrentThread.ManagedThreadId
获取当前线程Id
-
async
的方法会被C#编译器编译为一个类, 主要根据await调用进行切分 -
切分为多个状态, 对
async
方法的调用会被拆分为对MoveNext
的调用 -
用
await
看似等待, 实则经过编译后, 并没有wait -
await
调用等待期间, .net会把当前的线程返回给线程池, 等异步方法调用执行完毕后, 框架会从线程池再取出来一个线程执行后续的代码
3、异步与多线程
-
异步方法的代码并不会自动在新线程中执行, 除非代码放到新线程中执行
await Task.Run() => { 异步操作; }
4、async、await
异步方法缺点
-
异步方法会生成一个类, 运行效率没有普通方法高
-
可能会占用非常多的线程
-
返回值为
Task
的不一定都要标注async
, 标注async
只是让我们更方便的await
-
去掉
async
-
如果一个异步方法只是对别的异步方法调用的转发, 并没有太多复杂的逻辑(比如等待A的结果, 再调用B, 把A调用的返回值拿到内部去做一些处理再返回)
-
(1)去掉async、await
方式一: 甩手掌柜
-
方法0调用方法1返回
Task<T>
就不用返回加await
关键字了, 只是普通的方法调用 -
运行效率更高, 不会造成线程资源浪费
public async Task<string> WithAsync() { string text = await File.ReadAllTextAsync(@"f:\yzk\a\1.txt"); return text; }
public Task<string> WithoutAsync() { return File.ReadAllTextAsync(@"f:\yzk\a\1.txt"); }
(2)去掉async、await
方式二: 包装为Task返回
-
使用
Task.fromResult(ele)
方法, 包装返回值
public async Task<string> WithAsync() { return await Task.Run(() => { //操作 return result; }); }
public Task<string> WithoutAsync() { return Task.Run(() => { //操作 return Task.fromResult(result); }); }
5、异步方法中的暂停
-
如果在异步方法中暂停一段时间, 不要用
Thread.Sleep()
, 会阻塞调用线程 -
需要使用
await Task.Delay()
6、异步方法的取消-CancellationToken
结构体
-
有时需要提前终止任务, 比如请求超时,用户取消请求
-
很多异步方法都有
CancellationToken
参数, 用于获得提前终止执行的信号 -
他是结构体, 有如下成员
-
None
, 表示空, 因为结构体没法为空 -
IsCancellationRequested
是否发出了取消任务的请求bool
类型 -
Register(Action callback)
注册取消监听 -
ThrowIfCancellationRequested()
如果任务被取消, 执行到这句话就抛异常
-
-
通常使用
CancellationTokenSource
类来构造CancellationToken
, 这个类还有如下成员-
CancellationAfter()
超时后发出取消信号 -
Cancel()
手动发出取消信号 -
CancellationToken
获取提前结束异步任务令牌
-
public async static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(10000); CancellationToken token = cts.Token; await TestCancellationToken.DonwloadAsync("http://www.baidu.com", 10, token); }
-
从url 下载完n次 或者被暂停, 可以自定义资源取消后续处理
// 从url'下载完'n次或者被暂停, 可以自定义资源取消后续处理 public static async Task DonwloadAsync(string url, int n, CancellationToken cancellationToken) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { string html = await client.GetStringAsync(url); if (cancellationToken.IsCancellationRequested) { Console.WriteLine("取消了哈"); break; } } } }
-
从url 下载n次 或者被暂停, 不可自定义资源取消后续处理
//从url'下载'n次直到被暂停, 不可用定义 public static async Task DonwloadAsync(string url, int n, CancellationToken cancellationToken) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { HttpResponseMessage resp = await client.GetAsync(url, cancellationToken); string html = await resp.Content.ReadAsStringAsync(); Console.WriteLine($"{html}"); } } }
ASP.NET Core中的CancellationToken -- 能传则传, 应转尽转
-
在asp.net core中, 一般不需要自己处理
CancellationToken
-
只要做到能转发
CancellationToken
就转发即可, -
尽量做到能传则传, 应转尽转
-
asp.net core会对于用户请求中断进行处理
-
用于用户退出网站, 告知服务器不用继续处理了
//.net core 框架帮我们安排了CancellationToken, 不需要我们自己手动创建 public async Task Index(CancellationToken cancellationToken) { await TestCancellationToken.DonwloadAsync("http://www.baidu.com", 10, token); }
6、Task
类的重要方法
方法 | 描述 |
---|---|
Task<Task> WhenAny(IEnumberable<Task> task) |
任何一个Task 完成, Task 就完成 |
Task<TResult[]> WhenAll<TResult>(params Task<Result>[] tasks) |
所有Task 完成, Task 才完成 用于等待多个任务执行结束, 但不在乎他们的执行顺序 |
FromResult() |
创建普通数值的Task 对象 |
1、WhenAll()
public async Task TestTaskWhenAll() { Task<string> t1 = File.ReadAllTextAsync(@"f:\yzk\a\1.txt"); Task<string> t2 = File.ReadAllTextAsync(@"f:\yzk\a\2.txt"); Task<string> t3 = File.ReadAllTextAsync(@"f:\yzk\a\3.txt"); await Task.WhenAll(t1, t2, t3);//3个task完成, Task才完成, 等待多个任务执行完毕不在乎他们的执行顺序 }
例子: 汇总一个文件夹下所有文本文件的单词个数汇总
public async static Task Main() { string[] txts = Directory.GetFiles(@"f:\yzk\a"); Task<int>[] tasks = new Task<int>[txts.Length]; for (int i = 0; i < txts.Length; i++) { Task<int> t = TestTaskWhenAll.GetTxtWords(txts[i]); tasks[i] = t; } int[] txtCounts = await Task.WhenAll(tasks); int count = txtCounts.Sum(); Console.WriteLine($"{count}"); }
public async static Task<int> GetTxtWords(string url) { string txt = await File.ReadAllTextAsync(url); return txt.Length; }
7、接口中的异步方法不能用async
-
async
是提示编译器为异步方法中的await
代码进行分段处理的 -
而一个异步方法是否修饰了
async
对于方法的调用则来讲是没有区别的 -
因此对于接口中的方法或者抽象方法不能修饰为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2022-10-01 leetcode-sql-626. 换座位 order by if