文章目录
1. 异步编程介绍
1.1 简单介绍
异步编程官方参考文档:异步编程
1.2 async/await 使用
细节注意
- async 用来修饰方法,表示这个方法可以成为一个异步方法,但是如果内部没有搭配使用 await 关键字的话其作用还是等效于一个同步方法
- await 关键字必须用于在 async 修饰的异步方法内使用,await 的作用就是立即返回调用方法的结果,外部调用该 async 方法的地方将不会等待这个 await 关键字修饰 方法/其他延时 操作的完成 (拆分出主线程和子线程,子线程继续负责被调用函数执行,主线程遇到await 返回上一层线程当中)
但这个async 异步方法内,下面的操作还是必须要等待这个 await 关键字修饰 方法/其他延时 操作的完成.
class Program {
static void Main(string[] args) {
Console.WriteLine("This is methof(Main) starting ...");
Test();
Console.WriteLine("This is methof(Main) ending ...");
Console.ReadLine(); // 等待输入(卡住主线程, 不让主线程先结束)
}
static async void Test() {
Console.WriteLine("This is methof(Test) starting ...");
// 立即返回Test函数运行结果,继续Main函数运行. 并且同时会进行延时操作后再继续下面的方法.
await Task.Delay(3000);
Console.WriteLine("This is methof(Test) ending ...");
}
}
/*
This is methof(Main) starting ...
This is methof(Test) starting ...
// await Task.Delay(3000); 返回主线程运行 + 当前Test()方法此位置暂停运行3s
This is methof(Main) ending ...
This is methof(Test) ending ...
*/
1.3 Task/Task 对象
简单说明
- 使用Task或Task对象来保存正在运行的任务, 可以使用这个对象来保存一个正在运行的任务.
class Program {
static void Main(string[] args) {
Task<int> task = Task.Run(() => {
Console.WriteLine("Hello world ..");
return 2024;
});
int sum = task.Result;
Console.WriteLine(sum);
}
}
/*
Hello world ..
2024
*/
1.4 细节
简单说明
- Main方法使用异步需要 static async Task Main(string[] args)
- 父线程必须对子线程的执行结果进行保证,可以在父线程启动多个子线程 或者 只启动一个子线程,但这些线程的结果必须环环相扣,如果不使用 await 去保证线程之间调用结果的回调则有可能出现 父线程执行完成时子线程还未完成(这样就会导致本应子线程处理的任务被强行关闭).
例如1.2 中 Console.ReadLine(); // 等待输入(卡住主线程, 不让主线程先结束)
class Program {
static void Main(string[] args) {
Console.WriteLine("This is methof(Main) starting ...");
Test();
Console.WriteLine("This is methof(Main) ending ...");
// 注释本行代码则Main线程遇到 Test当中的 awaait就会回调执行, 会导致 Main线程早于子线程执行完毕.(子线程被强制中断)
// Console.ReadLine();
}
static async void Test() {
Console.WriteLine("This is methof(Test) starting ...");
await Task.Delay(3000);
Console.WriteLine("This is methof(Test) ending ...");
}
}
/*
This is methof(Main) starting ...
This is methof(Test) starting ...
This is methof(Main) ending ... (主线程回调直接执行完结果)
*/
父线程调用异步子线程时必须等待,否则在遇到子线程当中 await方法回调后若父线程执行过快则可能导致子线程操作不会执行. 例如如果想在该子线程下完成实体的新建(因为父线程未进行等待,此新建操作压根不会执行)
这里还有一个关键点,当使用异步编程去优化主线程,提高应用的响应性和性能时需要确保底层系统交互时的服务/接口是支持异步的,只有其支持异步才能在本地完成主线程的释放操作,例如 如果将以下示例代码当中的 var resp = (ExecuteMultipleResponse) await service.ExecuteAsync(requestWithResults); 这一行代码替换成一些同步函数 service.Create(xxx) 的话这里是无法支持异步编程的.
class Program {
static string connectionString = $"""""";
static async Task Main(string[] args) {
Console.WriteLine("This is methof(Main) starting ...");
ServiceClient service = new ServiceClient(connectionString);
Test(service); // 这里父线程没有进行等待子异步任务
Console.WriteLine("This is methof(Main) ending ...");
}
static async Task Test(ServiceClient service) {
Console.WriteLine("This is methof(Test) starting ...");
await Task.Delay(3000); // 遇到await父线程回调, 因为父线程执行过快,下面的新建实体操作则压根不会执行
Console.WriteLine("子线程无法执行到此位置");
OrganizationRequestCollection requests = new OrganizationRequestCollection();
for (int i = 0; i < 3; i++) {
Entity x = new Entity("new_response");
x["new_question001"] = "satified" + "hhhh" + i.ToString();
requests.Add(new CreateRequest(){ Target = x});
}
var requestWithResults = new ExecuteMultipleRequest() {
Settings = new ExecuteMultipleSettings() {
ContinueOnError = true,
ReturnResponses = true
},
Requests = requests
};
var resp = (ExecuteMultipleResponse) await service.ExecuteAsync(requestWithResults);
Console.WriteLine(resp);
}
}
2. 样例
2.1 主线程启动多个任务,需要等待结果时再 await 所有任务 Task.WhenALL
等待多个线程任务执行完成
class Program {
static async Task Main(string[] args) {
int[] nums = { 12, 22, 32, 42, 52 };
List<Task> tasks = new List<Task>();
for(int i = 0; i < nums.Length; i++ ) {
tasks.Add(RunAsyncTask(nums[i]));
}
await Task.WhenAll(tasks); // 等待所有任务(大概3s即可执行这5个任务)
}
static async Task RunAsyncTask(int k) {
Console.WriteLine("This is number " + k + " starting.... ");
await Task.Delay(3000);
Console.WriteLine("This is number " + k + " ending.... ");
}
}
/*
This is number 12 starting....
This is number 22 starting....
This is number 32 starting....
This is number 42 starting....
This is number 52 starting....
# 大约3s 等待时间 #
This is number 42 ending....
This is number 32 ending....
This is number 52 ending....
This is number 12 ending....
This is number 22 ending....
*/
2.2 遇到 await 方法会分出两个线程,一个继续当前线程执行,另一个会返回调用线程(但是如果调用线程本身用await修饰,则调用线程依旧会卡着)
class Program {
// 执行Main 方法为 Main线程
static void Main(string[] args) {
Test(); // Main线程进入其中
Console.WriteLine("This is Main Finish .....");
Console.ReadLine(); // 这里是为了卡着Main线程,不让Main线程结束(否则看不到其他线程结果)
}
// 执行Test 方法为 Test线程
static async void Test() {
await playGameAsync(); // Main 线程遇到 await, 会拆分出 Test线程. 此时, Main线程回调到Main方法,Test线程继续执行 playGameAsync()方法
Console.WriteLine("==========================================================");
Console.WriteLine("i am washing right now ...");
}
// 执行 playGameAsync 为 play线程
static async Task playGameAsync() {
await Task.Run(() => { // Test 线程来这也会拆分两个线程(但是play线程回调时因为自己被await修饰了, 所以不能像Main线程一样立即向下执行),需要等待 play线程执行完成
Task.Delay(3000).Wait();
Console.WriteLine("I am play game ..");
});
Task.Delay(3000).Wait();
Console.WriteLine("i am finish play game");
}
}
/*
This is Main Finish .....(等待3s)
I am play game ..
i am finish play game (明显发现 finish play game 的输出顺序会优先于 ====== )
==========================================================
i am washing right now ...
*/
2.3 业务使用异步编程 -> 需要确保与CRM 系统进行大量数据交互或复杂业务逻辑处理时其相关接口/函数是支持异步操作的 (确保其环境支持异步操作)
详细案例可以见上述 1.4 案例当中的新建操作,如果替换成为 OrganizationServi.Create() 操作则会导致其异步编程失效,
因为这个数据交互的接口是同步接口,无法支持异步相关操作.
3. 补充拓展说明