- 异步方法的返回值一般是Task,T是真正的返回值类型,Task.惯例:异步方法名字最好以Async结尾,这样方便我们只要是async
- 即使方法没有返回值,也最好吧返回值声明为非泛型的Task.
- 调用泛型方法时,一般在方法前加上await关键字,这样拿到的返回值就是泛型指定的T类型;
- 异步方法的"传染性": 一个方法中如果await调用,则这个方法也必须修饰为async
static async Task Main(string[] args)
{
string fileName = "d:/1.txt";
File.Delete(fileName);
File.WriteAllTextAsync(fileName,"hello async");
string s = await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
static async Task Main(string[] args)
{
string fileName = @"F:a1.txt";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.AppendLine("hello");
}
//这里如果不加await,会显示异常 因为还没写入完成就开始读取,读取是独占的
await File.WriteAllTextAsync(fileName, sb.ToString());
string s = await File.ReadAllTextAsync(fileName);
//Task t = File.ReadAllTextAsync(fileName);
//string s = await t;
Console.WriteLine(s);
}
如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法. .Net5中,很多框架的方法也都支持异步:Main,WinForm时间处理函数.
对于不支持异步方法该怎么办?
Wait()(无返回值); Result(有返回值) 风险:死锁,尽量不用
static async Task Main(string[] args)
{
int l = await DownloadHtmlAsync("https://www.youzack.com", @"F:a1.txt");
Console.WriteLine("ok" + l);
}
static async Task DownloadHtmlAsync(string url, string fileame)
{
using (HttpClient httpClient = new HttpClient())
{
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(fileame, html);
return html.Length;
}
}
二. await关键字修饰的方法
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池在取出来一个线程执行后续的代码.
Thread.CurrentThread.ManageThreadId 获得当前线程Id
验证:在耗时异步(写入大字符串)操作前后分别打印线程Id
static async Task Main(string[] args)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append("XXXXXXXXXXXXXXXXX");
}
await File.WriteAllTextAsync(@"F:a1.txt", sb.ToString());
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
优化:到要等待的时候,如果发现已经执行结束了,那就没必要再切换线程了,剩下的代码就继续在之前的线程上就行执行了
三.异步方法不等于多线程static async Task Main(string[] args)
{
Console.WriteLine("之前" + Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
Console.WriteLine($"r = {r}");
Console.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task CalcAsync(int n)
{
Console.WriteLine("CalcAsync," + Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random rand = new Random();
for (int i = 0; i < n*n; i++)
{
result += rand.NextDouble();
}
return result;
}
结论: 异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行.
把要执行的代码以委托的形式传递给Task().Run().这样就会从线程池中出去一个线程执行我们的委托
await Task.Run(()=>{
//耗时操作代码,可以返回return返回值
})
四. 没有async的异步方法
async方法缺点:
- 异步方法会生成一个类,运行效率没有普通方法高
- 可能占用非常多的线程
static async Task Main(string[] args)
{
string s = await ReadAysnc(1);
Console.WriteLine(s);
}
static Task ReadAysnc(int num)
{
if (num == 1)
{
return File.ReadAllTextAsync(@"F:a1.txt");
}
else if (num == 2)
{
return File.ReadAllTextAsync(@"F:a2.txt");
}
else
{
throw new ArgumentException();
}
}
从上面代码分析来看,只甩手Task,不"拆完了再装"反编译上面的代码: 只是普通方法调用
优点: 运行效率更高,不会造成线程浪费五.暂停调用方法使用返回值为Task的不一定都要标注async,标注async只是让我们可以更方便的await而已
如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字.
如果想在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay().
举例:下载一个网址,3秒后下载另一个
namespace 异步休息
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
using (HttpClient httpClient = new HttpClient())
{
string s1 = await httpClient.GetStringAsync("https://www.youzack.com");
textBox1.Text = s1.Substring(0, 100);
//Thread.Sleep(3000);
await Task.Delay(3000);
string s2 = await httpClient.GetStringAsync("https://www.baidu.com");
textBox1.Text = s2.Substring(0, 100);
}
}
}
}
在控制台中没看到区别,但是放到WinForm程序中就看到区别了,ASP.NET Core中也看到区别,但是Sleep()会降低并发.
六.CancellationToken参数有时需要提前终止任务,比如:请求超时,用户取消请求.
很多异步方法都有CancellationToken参数,用户获得提前终止执行的信号.
CancellationToken结构体 none:空 bool isCancellationRequested 是否取消 (*)Register(Action callback) 注册取消监听 ThrowCancellationRequested() 如果任务被取消,执行到这句话就抛异常
CancellationTokenSource CancelAfter()超时后发出取消信号 Cancel()发出取消信号 CancellationToken Token
为"下载一个网址N次"的方法增加取消功能,分别用GetStringAsync + isCancellationRequested,GetStringAsync + ThrowIfCancellationRequested(),带 CancellationToken的GetStringAsync ()分别实现.取消分用超时,用户敲按键(不能await)实现.
class Program
{
static async Task Main1(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
await Download2sAsync("https://www.baidu.com", 100, cts.Token);
while (Console.ReadLine() != "q")
{
}
cts.Cancel();
Console.ReadLine();
}
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
await Download2sAsync("https://www.baidu.com", 100,cts.Token);
}
static async Task Download2sAsync(string url, int n,CancellationToken cancellationToken)
{
using (HttpClient httpClient = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await httpClient.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
await Task.Delay(500);
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
}
}
}
}
ASP.NET Core开发中,一般不需要自己处理CancellationToken,CancellationTokenSource这些,只要做到"能转发CancellationToken就转发即可".ASP.NET Core会对于用户请求中断进行处理.
七.Task类的重要方法- Task WhenAny(IEnumerable tasks)等,任何一个Task完成,Task就完成
- Task
WhenAll(params Task[] tasks)等,所有Task完成,Task才完成,用于等待多个任务执行结束,但是不在乎他们的执行顺序. - FromResult()创建普通数值的Task对象.
async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲没区别的,因此对于接口中的方法或者抽象方法不能修饰为async
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
interface ITest
{
Task GetCharCount(string file);
}
class Test : ITest
{
public async Task GetCharCount(string file)
{
string s = await File.ReadAllTextAsync(file);
return s.Length;
}
}
}
2.异步与yield
回顾:yield return不仅能够简化数据的返回,而且可以让数据处理"流水线化",提升性能
static IEnumerableTest1() { List list = new List (); list.Add("hello1"); list.Add("hello2"); list.Add("hello3"); return list; } static IEnumerable Test2() { yield return "hello1"; yield return "hello2"; yield return "hello3"; }
效果是一样的.
在旧版C#中,async方法中不能用yield.从C# 8.0开始,吧返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候await foreach()即可
static async Task Main(string[] args)
{
await foreach (var s in Test3())
{
Console.WriteLine(s);
}
}
static async IAsyncEnumerable Test3()
{
yield return "hello1";
yield return "hello2";
yield return "hello3";
}
ASP.NET Core和控制台项目中没有SynchronizationContext,因此不用管ConfigureAwait(false)等.不要同步,异步混用



