特点/原理
特点
- 异步编程并不能提高运行效率,只能提高同时处理任务的速率(并发)
- 异步方法返回值一般是Task
,其中T就是返回值的类型 - 异步方法一般命名需要在名称后加上Async
- 如果异步方法没有返回值,void也要写成Task
- 在调用异步方法时,需要加上await关键字修饰,如果没加上await关键字调用异步方法,程序则不会等待,直接执行下一行代码,并且得到的返回值会是Task
- 异步方法具有传染性,如果使用了await关键字,方法也必须使用async关键字修饰
- 异步委托也需要加上async关键字修饰
- 不能使用out、ref参数 ```csharp string filename = @”D://1.txt”; await File.WriteAllTextAsync(filename, “hello async await”); string s = await File.ReadAllTextAsync(filename);
ThreadPool.QueueUserWorkItem( async (obj) => { await File.ReadAllTextAsync(“D://1.txt”); });
<a name="lHNRO"></a>
## 执行原理
- 在执行异步方法时,异步方法的代码会被拆分了对MoveNext方法的多次调用
- 在await等待的过程中,如果执行时间较长,异步调用前的线程被返回线程池中,等待结束后会返回新的线程,后续的代码会在这个新的线程中继续运行
- 在不影响系统并发的情况下,尽量减少线程切换
```csharp
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//打印线程ID
StringBuilder sb = new StringBuilder();
//构建一个大字符串进行耗时操作
for (int i = 0; i < 10000; i++)
{
sb.Append("dfasdgdgsaafdsgfdahgdf");
}
await File.WriteAllTextAsync("D://1.txt",sb.ToString());//调用异步方法
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//打印线程ID
异步方法注意事项
异步不等于多线程
如果方法只是修饰了async关键字,并不代表会切换到新的线程,方法内使用了await关键字,才会按照异步执行,否则只是还是会按照同步方法执行,如果需要手动新开线程,可以使用Task.Run();方法或Task.Factory.StartNew方法进行。可以理解成,方法只是修饰了async,返回值是Task/Task
没有async的异步方法
await的作用相当于把返回类型Task
string outsrt = await ReadFileAsync(1);
Console.WriteLine(outsrt);
Task<string> ReadFileAsync(int num)
{
switch (num)
{
case 1:
return File.ReadAllTextAsync("D://1.txt");
case 2:
return File.ReadAllTextAsync("D://3.txt");
default:
throw new ArgumentException("num invalid");
}
}
上方是异步的方式调用没有async修饰的异步方法,下方是同步方法中调用异步方法,但尽量不这么做,会存在风险:死锁
string s1 = File.ReadAllTextAsync().Result;//有返回值
string s2 = File.ReadAllTextAsync().GetAwaiter().GetResult();//有返回值
File.WriteAllTextAsync().Wait();//无返回值
综上所述,Task才是异步方法的精髓,async/await是微软提供的语法糖,使得用户更好的使用异步方法。
异步编程中的暂停
不可以使用Thread.Sleep()方法,否则会阻塞线程,而要使用Task.Delay()。
CancellationToken
作用:异步方法传入CancellationToken值,用于中断执行操作,比如调用了一个长时间运算的方法,但是中途中断了(比如浏览器关了),如果没有CancellationToken,服务器不会停止运算,造成资源浪费,而传入CancellationToken后,会根据设定的规则停止当前线程运行。
ASP.NET CORE的异步方法中,有CancellationToken的方法尽量都传
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
DownloadAsync("https://www.youzack.com",1000,cancellationToken);
}
static async Task DownloadAsync(string url,int n,CancellationToken cancellationToken)
{
using (HttpClient client = new HttpClient())
{
for(int i =0;i < n; i++)
{
var resp = await client.GetAsync(url,cancellationToken);
string html = await resp..Content.ReadAsStringAsync();
Debug.WriteLine(html);//ASP.NET中使用Debug打印内容,输出到输出窗口
}
}
}
等待多个Task运行
WhenAny
WhenAll
与,全都完成了Task才完成,用于等待多个任务执行结束,但是不在乎它们的执行顺序。
FormResult
创建普通数值的Task对象
Task<string> t1 = File.ReadAllTextAsync("D://1.txt");
Task<string> t2 = File.ReadAllTextAsync("D://2.txt");
Task<string> t3 = File.ReadAllTextAsync("D://3.txt");
//Task.WhenAll
string[] results = await Task.WhenAll(t1,t2,t3);
foreach (var ddd in results)
{
Console.WriteLine("数据"+ddd);
}
//Task.WhenAny
var results2 = await Task.WhenAny(t1,t2,t3);
var fasdf = results2.Result;
//FormResult
string ddddd = await Task.FromResult<string>("fdsfasd");
其他问题
在接口(interface)中不需要修饰async
yield return与async一起使用,返回值要设置成IAsyncEnumerable<>,不加Task,调用时在foreach前要加上await
异步,同步不要混用