- 异步和多线程不是同一个概念,异步相当于餐厅服务员给你递给菜单,然后剩下的你自己看菜单,看完再叫服务员(服务员只负责给客人递菜单,递完一个到下一个那边),而多线程相当于n个服务员对应n个客人,一对一服务。
- 异步方法具有传染性,await会阻塞当前异步线程,直到异步方法执行完毕才开始执行后续的代码逻辑。
- 不写async的方法 有返回值:异步方法().Result 无需写await了手动取值无返回值:异步方法().Wait() 容易造成死锁,谨慎使用
- 异步Lambda表达式 在obj前面加上async,在表达式前面加上async。
- await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。
- 如果一个异步方法只是对别的异步方法的调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字。
- 利用await Task.Delay(TimeSpan.FromSeconds(2))来替代Thread.Sleep()
- 在ASP.NET开发中,只要有cancellationtoken参数就传入。Action方法都传入CancellationToken token参数,调用异步方法能传token都传这个参数。
- Task.WhenAll Task.WhenAny
- yield return和异步 async IAsyncEnumerable
返回值 MethodName() 调用的时候await foreach一 基础概念
异步方法”:用async关键字修饰
1.返回值一般是TaskT是真正的返回值类型,异步方法名字以Aysnc结尾
2.即使方法没有返回值,也最好把返回值申明为非泛型的Task
3.调用泛型方法时,一般在方法前加上await,这样拿到的返回值就是泛型指定的T类型。
4.异步方法具有传染性:一个方法中如果有await调用,则这个方法必须修饰为async。
主函数Main里面调用了异步方法,void必须更改为Task,如下:
await可以将返回值从Task
static async Task Main(string[] args)
{
//string fileName = @"E:\albert.txt";
//File.WriteAllText(fileName, "Hello");
//string s = File.ReadAllText(fileName);
//Console.WriteLine(s);
string fileName = @"E:\albert.txt";
await File.WriteAllTextAsync(fileName, "Albert");
//这里面加了await把Task<string>中的返回值取出来了
/*
* 第二种写法:
Task<string> t = File.ReadAllTextAsync(fileName);
string s = await t;
*
*/
string s = await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
Console.ReadLine();
}
二 异步方法(Self Create)
HttpClient最好和HttpFactory一起使用
HttpClient实现了IDisposable所以需要写using进行回收
static async Task Main(string[] args){
string filename = @"E:\albert.txt";
await DownloadHtmlAysnc("http://www.baidu.com",filename);
}
/// <summary>
/// 从某个网站下载html到文件中
/// </summary>
/// <param name="url"></param>
/// <param name="filename"></param>
/// <returns></returns>
static async Task DownloadHtmlAysnc(string url,string filename)
{
//最好和HttpFactory一起使用
//HttpClient实现了IDisposable所以需要写using进行回收
using (HttpClient httpClient = new HttpClient())
{
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(filename, html);
}
}
三 如果调用方不支持异步方法
有返回值:异步方法().Result 无需写await了手动取值
无返回值:异步方法().Wait() File.WriteAllTextAsync(path,”Albert”).Wait();
容易造成死锁,谨慎使用
//手动取值
string s2 = File.ReadAllTextAsync(fileName).Result;
//Wait(),无返回值的情况
File.WriteAllTextAsync(path,"Albert").Wait();
四 异步委托:异步Lambda表达式
在委托中使用,异步Lambda表达式 在obj前面加上async。
//异步Lambda表达式
ThreadPool.QueueUserWorkItem(async obj =>
{
await File.WriteAllTextAsync(fileName, "xxxx");
});
五 Async Await原理揭秘
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
static async Task Main(string[] args){
using(HttpClient httpClient = new HttpClient()){
string html = await httpClient.GetStringAsync("http://www.google.com");
Console.WriteLine(html);
}
string txt = "Hello ALbert";
string filename = @"E:/albert.txt";
await File.WriteAllTextAsync(filename,txt);
Console.WriteLine("写成功");
string s = await File.ReadAllTextAsync(filename);
Console.WriteLine("读成功”);
}
语法糖,最终编译成状态机的调用。async方法会被C#编译器编译成一个类,会根据await调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。
六 Async Await背后的线程调度
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。(未必是同一个线程)Tread.CurrentThread.ManagedThreadID
很多个客户点菜,给你递菜单的服务员等你点好呼叫服务员,可能是之前那个,也可能不是。而不需要像多线程那样每个客人对应一个服务员。线程可以得到充分的利用。到要等待的时候,如果发现已经执行结束了,就没必要切换线程浪费开销了。
异步执行的代码并不自动在新线程中执行,除非手动把代码放到新线程中执行。Task.Run(()=>{});可以切线程。
static async Task<double> ReadRandom()
{
//同步写法:
//Console.WriteLine($"我是主线程:{Thread.CurrentThread.ManagedThreadId}");
//double s = new Random().NextDouble();
//return s;
//使用await Task.Run(()=>{})开启异步,跨线程池
//自动匹配返回值类型
return await Task.Run(() => {
Console.WriteLine($"我是主线程:{Thread.CurrentThread.ManagedThreadId}");
double s = new Random().NextDouble();
return s;
});
}
七 无async的异步方法
async把返回值包装成Task
async会生成一个类,然后通过状态机MoveNext()进行状态的切换,可能造成线程切换和资源消耗。如果在有必要情况下,不写async。不做拆完后再装的操作。只是一个普通的方法调用。性能效率更高,不会造成线程资源的浪费。
如果一个异步方法只是对别的异步方法的调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字。
static async Task<string> ReadFileAsync(int num)
{
switch (num)
{
//ReadAllTextAsync返回Task<string> await将string值取出来,返回给调用
//函数ReadFileAsync,ReadFileAsync通过async将string包装成Task<string>
case 1:
return await File.ReadAllTextAsync(@"E:\albert.txt");
case 2:
return await File.ReadAllTextAsync(@"E:\2.txt");
default:
throw new ArgumentException("num invalid");
}
}
static Task<string> ReadFileSecondAsync(int num)
{
switch (num)
{
//ReadAllTextAsync返回Task<string> await将string值取出来,返回给调用
//函数ReadFileAsync,ReadFileAsync通过async将string包装成Task<string>
case 1:
return File.ReadAllTextAsync(@"E:\albert.txt");
case 2:
return File.ReadAllTextAsync(@"E:\2.txt");
default:
throw new ArgumentException("num invalid");
}
}
八 不要Thread.Sleep()
利用await Task.Delay(TimeSpan.FromSeconds(2))来替代Thread.Sleep()
Thread.Sleep()会阻塞当前线程
await Task.Delay()方式可以有效减少线程的消费,是创建高伸缩性的服务器程序的关键。
在winform程序中,点击事件可以改写成异步方法,但是void不需要变为Task,因为那是事件(委托)规定好的
Button_Click不是在主线程中,而是异步线程,如果用的是Thread.Sleep()则会阻塞主线程
await Task.Sleep()阻塞的是异步线程。Sleep会阻塞Web服务器线程,线程会卡住。
private async void Button1_Click(object sender,EventArgs e){
using(HttpClient httpClient = new HttpClient()){
string s1 = await httpClient.GetStringAsync("http://www.baidu.com");
this.textbox1.Text = s1.Substring(0,20);
//Thread.Sleep(3)阻塞的是主线程
await Task.Delay(TimeSpan.FromSeconds(3));//阻塞的是MoveNext切状态
string s2 = await httpClient.GetStringAsync("http://www.baidu.com");
this.textbox1.Text = s2.Substring(0,20);
}
}
九 CancellationToken
在ASP.NET开发中,只要有cancellationtoken参数就传入。Action方法都传入CancellationToken token参数,调用异步方法能传token都传这个参数。
namespace _210712_Demo01_Aysnc
{
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(5000);
await DownloadHtml("https://www.baidu.com", 10000, cancellationTokenSource.Token);
}
static async Task DownloadHtml(string url,int downloadCtn,CancellationToken cancellationToken)
{
using(HttpClient httpClient = new HttpClient())
{
for (int i = 0; i < downloadCtn; i++)
{
string html = await httpClient.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
//第二种方式html内容的方式,可能在5s没到就取消请求了,而不需要一直等到请求后才能处理
var resp = await httpClient.GetAsync(url, cancellationToken);
string htmlTwo = await resp.Content.ReadAsStringAsync();
//此种方案无法实现立即取消
//if (cancellationToken.IsCancellationRequested)
//{
// Console.WriteLine("请求被取消");
// break;
//}
}
}
}
}
}
public async Task<IActionResult> Index(CancellationToken token)
{
await DownloadAsync("https://www.baidu.com", 10000, token);
return View();
}
十 Task重要方法
Task.WhenAny(IEnumerable
Task.WhenAll(params Task
//开启n个Task,然后将Task放在Task数组中,用WhenAll等待所有数组Task执行完毕
namespace _20170713_Demon01_TaskWhenAll
{
class Program
{
static async Task Main(string[] args)
{
string[] filename = Directory.GetFiles(@"C:\01_AlbertCode\NET\NETCoreWinform");
Task<int>[] countTask = new Task<int>[filename.Length];
//开启n个Task,然后将Task放在Task数组中,用WhenAll等待所有数组Task执行完毕
for (int i = 0; i < filename.Length; i++)
{
string file = filename[i];
Task<int> t = ReadCharCount(file);
countTask[i] = t;
}
int[] counts = await Task.WhenAll(countTask);
Console.WriteLine(counts.Sum());//计算数组的和 Linq
Console.ReadLine();
}
//将文件夹下所有文件的字符个数汇总出来
static async Task<int> ReadCharCount(string filename)
{
string s = await File.ReadAllTextAsync(filename);
return s.Length;
}
}
}
FromResult()创建普通数值的Task对象:异步方法中在没有声明async的情况下使用:(容易造成死锁)
//手动取值
string s2 = File.ReadAllTextAsync(fileName).Result;
//Wait(),无返回值的情况
File.WriteAllTextAsync(path,"Albert").Wait();
十一 零碎知识点
- 接口/抽象类的方法不能修饰为async
- 异步与yield yield return不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。yield return按需供给 yield return 会再调用的时候才会供给,反编译拆成三段。在旧版,异步方法无法用yield。从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。
```csharp
//两者等价,第一种方法需要额外开辟空间,而第二个则按需供给。
static IEnumerable
Test(){ List list = new List (); list.Add(“Hello”); list.Add(“Yzk”); list.Add(“Youzack”); }
static IEnumerable
static IAsyncEnumerable
static async Task Main(string[] args){ await foreach(var s in Test()){ Console.WriteLine(s); } } ```
- ASP.NET Core和控制台项目中没有SynchronizationContext,因此不用管ConfigureAwait(false)等。不要同步异步混用。在Winform和WPF中依旧存在。
十二 疑惑点
1.异步方法可以重载吗?
2.如何避免不支持异步方法调用方造成的死锁?
3.状态机模式的理解?
4.为什么要把一个async方法拆分为多个状态然后分为多次调用?
5.异步的可以避免线程等待耗时操作但是await还是等待呀?
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。(未必是同一个线程)Tread.CurrentThread.ManagedThreadID
很多个客户点菜,给你递菜单的服务员等你点好呼叫服务员,可能是之前那个,也可能不是。而不需要像多线程那样每个客人对应一个服务员。线程可以得到充分的利用。到要等待的时候,如果发现已经执行结束了,就没必要切换线程浪费开销了。