本篇文章并不适合小白阅读
倘若想要进行学习请先阅读:CSharp(C#)语言_高级篇(异步编程)【划重点咯】 打下一个良好的基础再进行阅览
文中用到的反编译工具 ILSpy 免费且开源的,可自行下载
//static async Task Main(string[] args)
static void Main(string[] args)
{
string fileName = $"./1.txt";
//StringBuilder stringBuilder = new StringBuilder();
//for (int i = 0; i < 100000; i++)
//{
// stringBuilder.Append("Hello ");
//}
/* 同步方式 */
//File.WriteAllText(fileName, stringBuilder.ToString());
//Console.WriteLine(File.ReadAllText(fileName));
/*
异步方式 需要在方法标识符 添加 Async 并且 返回值必须为 Task 类型
异步方法调用前加 await
*/
//await File.WriteAllTextAsync(fileName, stringBuilder.ToString());
//Console.WriteLine(await File.ReadAllTextAsync(fileName));
// 同步方法中调用异步方法
// 有返回值可以使用 Result 属性 取到异步方法返回值 而不用 await 标识
// 无返回值可以使用 Wait() 方法
//Console.WriteLine(DownloadHtmlAsync("https://www.baidu.com", fileName).Result);
// 有一定的产生死锁的风险
/*
异步 lamdba 表达式
使用 async 将 lambda 表达式 修饰为 异步 lambda 表达式
*/
ThreadPool.QueueUserWorkItem(async (obj) =>
{
while (true)
{
await File.WriteAllTextAsync(fileName, "Hello");
Console.WriteLine("Hello");
}
});
Console.Read();
}
//static async Task DownloadHtmlAsync(string url, string fileName)
//{
// using HttpClient httpClient = new();
// string html = await httpClient.GetStringAsync(url);
// await File.WriteAllTextAsync(fileName, html);
//}
static async Task<int> DownloadHtmlAsync(string url, string fileName)
{
using HttpClient httpClient = new();
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(fileName, html);
return html.Length;
}
async await 是语法糖 最终编译成 “状态机调用”
async 的方法会被 C# 编译器编译成一个类,会主要根据await调用进行切分为多个状态,对 async 方法的调用会被拆分为对 MoveNext 的调用。
用 await 看似是 “等待”,经过编译后,其实没有“wait”
static async Task Main(string[] args)
{
using HttpClient client = new();
string html = await client.GetStringAsync("https://www.baidu.com");
Console.WriteLine($"{html}\n");
string destFilePath = $"./1.txt";
await File.WriteAllTextAsync(destFilePath, "你好 async and await");
Console.WriteLine($"写入内容:{await File.ReadAllTextAsync(destFilePath)}");
}
原始 Main 方法反编译代码
// AsyncAwait.Program
using System.Diagnostics;
using System.Runtime.CompilerServices;
[SpecialName]
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
Main(args).GetAwaiter().GetResult();
}
异步 Main 方法源代码编译之后反编译代码
// AsyncAwait.Program
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
[AsyncStateMachine(typeof(<Main>d__0))]
[DebuggerStepThrough]
private static Task Main(string[] args)
{
<Main>d__0 stateMachine = new <Main>d__0();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.args = args;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
async await 底层 “状态机” 反编译代码
// AsyncAwait.Program.d__0
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Runtime.CompilerServices;
[CompilerGenerated]
private sealed class <Main>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public string[] args;
private HttpClient <client>5__1;
private string <html>5__2;
private string <destFilePath>5__3;
private string <>s__4;
private string <>s__5;
private TaskAwaiter<string> <>u__1;
private TaskAwaiter <>u__2;
private void MoveNext()
{
int num = <>1__state;
try
{
if ((uint)num > 2u)
{
<client>5__1 = new HttpClient();
}
try
{
TaskAwaiter<string> awaiter3;
TaskAwaiter awaiter2;
TaskAwaiter<string> awaiter;
switch (num)
{
default:
awaiter3 = <client>5__1.GetStringAsync("https://www.baidu.com").GetAwaiter();
if (!awaiter3.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter3;
<Main>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
return;
}
goto IL_009e;
case 0:
awaiter3 = <>u__1;
<>u__1 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
goto IL_009e;
case 1:
awaiter2 = <>u__2;
<>u__2 = default(TaskAwaiter);
num = (<>1__state = -1);
goto IL_014b;
case 2:
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
break;
}
IL_014b:
awaiter2.GetResult();
awaiter = File.ReadAllTextAsync(<destFilePath>5__3).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 2);
<>u__1 = awaiter;
<Main>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
break;
IL_009e:
<>s__4 = awaiter3.GetResult();
<html>5__2 = <>s__4;
<>s__4 = null;
Console.WriteLine(<html>5__2 + "\n");
<destFilePath>5__3 = "./1.txt";
awaiter2 = File.WriteAllTextAsync(<destFilePath>5__3, "你好 async and await").GetAwaiter();
if (!awaiter2.IsCompleted)
{
num = (<>1__state = 1);
<>u__2 = awaiter2;
<Main>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
goto IL_014b;
}
<>s__5 = awaiter.GetResult();
Console.WriteLine("写入内容:" + <>s__5);
<>s__5 = null;
}
finally
{
if (num < 0 && <client>5__1 != null)
{
((IDisposable)<client>5__1).Dispose();
}
}
}
catch (Exception exception)
{
<>1__state = -2;
<client>5__1 = null;
<html>5__2 = null;
<destFilePath>5__3 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<client>5__1 = null;
<html>5__2 = null;
<destFilePath>5__3 = null;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
异步的 Main 方法 并不是 原始的 Main方法,从上面反编译的代码可以看出来
await调用的等待期间,.NET会把当前的线程返给线程池,等异步方法调用执行完毕后,框架会从线程池再取一个出来线程执行后续的代码。
Thread.CurrentThread.ManagedThreadId
查看线程Idstatic async Task Main(string[] args)
{
string fileName = $"./1.txt";
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
stringBuilder.Append("Hello ");
}
await File.WriteAllTextAsync(fileName, stringBuilder.ToString());
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
注意:如果写入内容少,会发生线程Id不变
CLI优化:到要等待的时候,如果发现已经执行结束了,那就没有必要再切换线程,剩下的代码就继续在之前的线程上继续执行。
static async Task Main(string[] args)
{
Console.WriteLine($"之前:{Thread.CurrentThread.ManagedThreadId}");
await CalcAsync(5000000);
Console.WriteLine($"之后:{Thread.CurrentThread.ManagedThreadId}");
}
static async Task<double> CalcAsync(int n)
{
/*
Console.WriteLine($"CalcAsync-ThreadId:{Thread.CurrentThread.ManagedThreadId}");
double result = 1;
Random random = new();
for (int i = 0; i < n * n; i++)
{
result += (double)random.NextDouble();
}
return result;
*/
return await Task.Run(() =>
{
Console.WriteLine($"CalcAsync-ThreadId:{Thread.CurrentThread.ManagedThreadId}");
double result = 1;
Random random = new();
for (int i = 0; i < n * n; i++)
{
result += (double)random.NextDouble();
}
return result;
});
}
/*
static async Task Main(string[] args)
{
Console.WriteLine(await ReadAsync(1));
}
*/
static void Main(string[] args)
{
Console.WriteLine(ReadAsync(1));
}
/*
static async Task ReadAsync(int num)
{
if (num is 1)
{
return await File.ReadAllTextAsync("./1.txt");
}
else if (num is 2)
{
return await File.ReadAllTextAsync("./2.txt");
}
else
{
throw new ArgumentNullException();
}
}
*/
static Task<string> ReadAsync(int num)
{
if (num is 1)
{
return File.ReadAllTextAsync("./1.txt");
}
else if (num is 2)
{
return File.ReadAllTextAsync("./2.txt");
}
else
{
throw new ArgumentNullException();
}
}
1、
async
返回会生成一个类,运行效率没有普通方法高;
2、可能会占用非常多的线程。
只甩手 Task,不 “拆完再装” 反编译上面的代码:只是普通的方法调用。
优点:运行效率更高,不会造成线程浪费。
返回值为 Task 的不一定都要标注 async,标注 async 只是让我们更方便的 await 而已。
如果一个异步方法只是对别的异步方法调用转发,并没有太多复杂的逻辑,那么就可以去掉 async 关键字
static void Main(string[] args)
{
Console.WriteLine($"之前:{Thread.CurrentThread.ManagedThreadId}");
CalcAsync(5000000);
Console.WriteLine($"之后:{Thread.CurrentThread.ManagedThreadId}");
}
static Task<double> CalcAsync(int n)
{
return Task.Run(() =>
{
Console.WriteLine($"CalcAsync-ThreadId:{Thread.CurrentThread.ManagedThreadId}");
double result = 1;
Random random = new();
for (int i = 0; i < n * n; i++)
{
result += (double)random.NextDouble();
}
return result;
});
}
如果想在异步方法中暂停一段时间,
不要用Thread.Sleep()
因为它会阻塞调用线程,
而要用await Task.Delay()
。
CancellationToken
结构体
None
:空
bool IsCancellationRequested
是否取消
(*)Register(Action callback)
注册取消监听
ThrowIfCancellationRequested()
如果任务被取消,执行到这句就抛异常。
通过
CancellationTokenSource
来创建CancellationToken
对象
Cancel()
发出取消信号
cts.CancelAfter()
超时后发去取消信号
static async Task Main(string[] args)
{
CancellationTokenSource cts = new();
cts.CancelAfter(1000);
CancellationToken token = cts.Token;
await DownloadAsync("https://www.baidu.com", 100, token);
}
///
/// 无取消请求型
///
///
///
///
static async Task DownloadAsync(string url, int n)
{
using HttpClient client = new();
for (int i = 0; i < n; i++)
{
Console.WriteLine($"{DateTime.Now}:{await client.GetStringAsync(url)}");
}
}
///
/// 取消请求型
///
///
///
///
///
static async Task DownloadAsync(string url, int n, CancellationToken token)
{
using HttpClient client = new();
for (int i = 0; i < n; i++)
{
// 手动处理型 推荐使用
Console.WriteLine($"{DateTime.Now}:{await client.GetStringAsync(url)}");
if (token.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
// 抛异常型,请求被终止抛出异常
//token.ThrowIfCancellationRequested();
// 抛异常型,将处理交予别人
//Console.WriteLine($"{DateTime.Now}:{await client.GetAsync(url, token)}");
}
}
ASP.NET Core 开发中,一般不需要自己处理
CancellationToken
CancellationTokenSource
这些,只要做到 能转发 CancellationToken 就转发 即可。ASP.NET Core 会对用户请求中断进行处理。
ASP.NET Core 程序中仅可能的在Action中使用 CancellationToken
以避免浏览器跳转到别的网页服务器还在执行而造成的资源浪费
Task类的重要方法
1、Task
等,任何一个 Task 完成,Task 就完成WhenAll(IEnumerable tasks)
2、Task
等,所有 Task 瓦纳城,Task 才完成。用于等待多份任务执行结束,但是不在乎他们的执行顺序。WhenAll (params Task [] tasks)
3、FromResult()
创建普通数值的 Task 对象。
static async Task Main(string[] args)
{
string[] files = Directory.GetFiles("./");
Task<int>[] countTasks = new Task<int>[files.Length];
for (int i = 0; i < files.Length; i++)
{
string fileName = files[i];
Task<int> task = ReadCharsCount(fileName);
countTasks[i] = task;
}
int[] counts = await Task.WhenAll(countTasks);
Console.WriteLine(counts.Sum());
}
static async Task<int> ReadCharsCount(string filenName)
{
string s = await File.ReadAllTextAsync(filenName);
return s.Length;
}
接口中的异步方法:
async 是提示编译器为异步方法中的 await 代码进行分段处理的,而一个异步方法是否修饰了 async 对于方法的调用者来讲没区别,因此对于接口中的方法或者抽象方法不能修饰为 async。
interface ITest
{
Task<int> GetCharCount(string file);
}
class Test : ITest
{
public async Task<int> GetCharCount(string file)
{
string s = await File.ReadAllTextAsync(file);
return s.Length;
}
}
异步与yield:
yield return 不仅能够简化数据的返回,而且可以让数据处理 “流水线化” 提升性能。
static void Main(string[] args)
{
foreach (var item in YieldTest())
{
Console.WriteLine(item);
}
}
static IEnumerable<string> YieldTest()
{
yield return "1";
yield return "2";
yield return "3";
}
在旧版的C#中,async 方法中不能用 yield。从C#8.0开始,把返回值声明为
IAsyncEnumerable
(不要带Task),然后遍历的时候用await foreach()
即可
static async void Main(string[] args)
{
await foreach (var item in YieldTest())
{
Console.WriteLine(item);
}
}
static async IAsyncEnumerable<string> YieldTest()
{
yield return "1";
yield return "2";
yield return "3";
}
不要同步、异步混用