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
对于方法的调用则来讲是没有区别的 -
因此对于接口中的方法或者抽象方法不能修饰为