aspcore系列5项⽬实战之:NetCore的async和await(参考
⾃:M。。。
⼗年河东,⼗年河西,莫欺少年穷
学⽆⽌境,精益求精
1、简介
从 VS 2012 开始,新引⼊了⼀个简化的⽅法,称为异步编程。我们在 >= .NETFRM 4.5 中和 Windows 运⾏时中使⽤异步,编译器它会帮助了我们降低了曾经进⾏的⾼难度异步代码编写的⼯作,但逻辑结构却类似于同步代码。因此,我们仅需要进⾏⼀⼩部分编程的⼯作就可以获得异步编程的所有优点。
对于同步的代码,⼤家肯定都不陌⽣,因为我们平常写的代码⼤部分都是同步的,然⽽同步代码却存在⼀个很严重的问题,例如我们向⼀个Web服务器发出⼀个请求时,如果我们发出请求的代码是同步实现的话,这时候我们的应⽤程序就会处于等待状态,直到收回⼀个响应信息为⽌,然⽽在这个等待的状态,对于⽤户不能操作任何的UI界⾯以及也没有任何的消息,如果我们试图去操作界⾯时,此时我们就会看
到”应⽤程序为响应”的信息(在应⽤程序的窗⼝旁),相信⼤家在平常使⽤桌⾯软件或者访问web的时候,肯定都遇到过这样类似的情况的,对于这个,⼤家肯定会觉得看上去⾮常不舒服。引起这个原因正是因为代码的实现是同步实现的,所以在没有得到⼀个响应消息之前,界⾯就成了⼀个”卡死”状态了,所以这对于⽤户来说肯定是不可接受的
2、优势
异步编程最⼤的优势其实就是提供系统执⾏效率,毕竟⼀个串⾏执⾏的程序不如并⾏来的快。譬如:⼀个⼈要⼲⼗件事情不如⼗个⼈各⼲⼀件事情效率⾼。
3、关键字
C# 中的 async 和 await 关键字都是异步编程的核⼼。通过使⽤这两个关键字,我们就可以在 .NET 轻松创建异步⽅法。
4、返回值类型
4.1、Void
如果在触发后,你懒得管,请使⽤ void。
void返回类型主要⽤在事件处理程序中,⼀种称为“fire and forget”(触发并忘记)的活动的⽅法。除了它之外,我们都应该尽可能是⽤Task,作为我们异步⽅法的返回值。
asp查看源码配置ui4.2、Task
你如果只是想知道执⾏的状态,⽽不需要⼀个具体的返回结果时,请使⽤Task。
与void对⽐呢,Task可以使⽤await进⾏等待新线程执⾏完毕。⽽void不需要等待。
4.3、Task<TResult>
当你添加async关键字后,需要返回⼀个将⽤于后续操作的对象,请使⽤Task<TResult>。
主要有两种⽅式获取结果值,⼀个是使⽤Result属性,⼀个是使⽤await。他们的区别在于:如果你使⽤的是Result,它带有阻塞性,即在任务完成之前进⾏访问读取它,当前处于活动状态的线程都会出现阻塞的情形,⼀直到结果值可⽤。所以,在绝⼤多数情况下,除⾮你有绝对的理由告诉⾃⼰,否则都应该使⽤await,⽽不是属性Result来读取结果值。
5、范例
再进⾏范例之前,先写⼀个错误的异步⽅法,如下:
public static async Task SyncExec_3()
{
Proc();
}
public static void Proc()
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine(i);
}
}
View Code
由上图截图可以,在异步⽅法内,需要使⽤await关键字,否则⽅法会同步执⾏。
不是说你把⼀个⽅法标记成async这个⽅法就成了异步调⽤的⽅法了。async这个关键词其实反⽽是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下⽂⽽已。
如下两个⽅法其实是⼀样的
Task<int> DelayAndCalculate1(int a, int b)
{
return Task.Delay(1000).ContinueWith(t => a + b);
}
async Task<int> DelayAndCalculate2(int a, int b)
{
await Task.Delay(1000);
return a + b;
}
View Code
那么,既然async是可以省略的,那么await可以省略吗?答案是不可以,否则你的⽅法会被编译警告,会成为⼀个同步⽅法。
其实真正重要的是await,有没有async反⽽确实不重要。既然微软提供了这样的语法糖,所以建议⼤家在写异步⽅法是加上async。
下⾯我们通过实例来说明异步编程,如下:
5.1、返回值为Task的程序具体返回了什么?
public static async Task SyncExec_2()
{
await Task.Run(() => {
Proc();
});
}
View Code
通过调试,快速监视,得到如下消息:
其实返回值为Task的⽅法中什么也没返回,但是我们确定接收到他的返回值,这点似乎是个⽭盾点。根据VS快速监视截图,我们发现我们接收的东西是⼀个上下⽂线程。
5.2、异步执⾏的顺序
核⼼代码:
我⽤短短⼏⾏实现了⼀个相对复杂的⼯作流,task直接的dependency很明确的表达在代码⾥,并且task1和task2可以并⾏执⾏,task3和task4可以并⾏执⾏。最后执⾏ doTask5。
详细代码如下:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace ConsoleCore
{
class Program
{
static void Main(string[] args)
{
var Result =  ComplexWorkFlow();
Console.WriteLine("执⾏结束");
Console.Read();
}
///<summary>
///加法
///</summary>
///<param name="num1"></param>
///<param name="num2"></param>
///<returns></returns>
public static int GetNum_Add(int num1, int num2)
{
return num1+ num2;
}
///<summary>
///减法
///</summary>
///<param name="num1"></param>
///<param name="num2"></param>
///<returns></returns>
public static int GetNum_Sub(int num1,int num2)
{
return num1 - num2;
}
/
//<summary>
///乘法
///</summary>
///<param name="num1"></param>
///<param name="num2"></param>
///<returns></returns>
public static int GetNum_Mul(int num1,int num2)
{
return num1 * num2;
}
///<summary>
/
//除法
///</summary>
///<param name="num1"></param>
///<param name="num2"></param>
///<returns></returns>
public static int GetNum_Cal(int num1, int num2)
{
return num1 / num2;
}
///<summary>
/// 10
/
//</summary>
///<returns></returns>
public static async Task<int> DoTask1()
{
var result = 0;
await Task.Run(() =>
{
result = GetNum_Add(5, 5);
});
Console.WriteLine("任务 DoTask1 执⾏完毕,结果为:" + result);
return result;
}
///<summary>
/// 90
///</summary>
///<returns></returns>
public static async Task<int> DoTask2()
{
var result = 0;
await Task.Run(() =>
{
result = GetNum_Sub(100,10);
});
Console.WriteLine("任务 DoTask2 执⾏完毕,结果为:" + result);
return result;
}
///<summary>
/// 100
///</summary>
///<param name="A"></param>
///<returns></returns>
public static async  Task<int> DoTask3UseResultOfTask1(int A)
{
var result = 0;
await Task.Run(() =>
{
result = GetNum_Mul(A, A);
});
Console.WriteLine("任务 DoTask3UseResultOfTask1 执⾏完毕,结果为:" + result);
return result;
}
///<summary>
/// 45
///</summary>
/
//<param name="A"></param>
///<returns></returns>
public static async Task<int> DoTask4UseResultOfTask2(int A)
{
var result = 0;
await Task.Run(() =>
{
result = GetNum_Cal(A, 2);
});
Console.WriteLine("任务 DoTask4UseResultOfTask2 执⾏完毕,结果为:" + result);
return result;
}
///<summary>
///两者平⽅和
///</summary>
///<param name="A"></param>
///<param name="B"></param>
///<returns></returns>
public static async Task<int> DoTask5(int A, int B)
{
var result = 0;
await Task.Run(() =>
{
result = A * A + B * B;
});
Console.WriteLine("最终的结果为:" + result);
return result;
}
public static async Task<int> ComplexWorkFlow()
{
Task<int> task1 = DoTask1();
Task<int> task2 = DoTask2();
Task<int> task3 = DoTask3UseResultOfTask1(await task1);
Task<int> task4 = DoTask4UseResultOfTask2(await task2);
return await DoTask5(await task3, await task4);
}
}
}
View Code
运⾏上述代码,我们会发现:可能存在如下结果:
发现没有:执⾏结束反⽽先执⾏完毕了,呵呵呵,意不意外?
继续执⾏⼀次:
这次执⾏结束换到了第⼆⾏,呵呵,意不意外?
再执⾏⼀次:
额,(⊙o⊙)…⼜跑到第三⾏了,不执⾏了,⼤概说下吧。
task1和task2并⾏,task3和task4并⾏,但是task1肯定会在task3之后,Task2肯定会在task4之后,task3 和 task4 肯定会在Task5之后,⾄于输出的执⾏结束这段话,可能出现在任何位置。哈哈。就这么任性。
上述探讨了异步⽅法,但:
⾸先要知道async await解决了什么问题,不要为了异步⽽异步,针对⾼密集的cpu计算异步没太⼤意义,甚⾄可能有性能损耗。
其次说async await的实现,就以你的代码为例,如果没有async await的话代码执⾏步骤就不说了,在有async await后就不⼀样,⼀旦调⽤⼀个async⽅法,就是告知,这⾥我可能需要点时间来处理,你先继续往后⾛吧(⽐如io操作),这块执⾏线程就会继续往后跑⽽不再关⼼async⽅法的返回直到看到对应的await后,就停下来等着await对应的task执⾏完(你async await的代码在编译后会变成⼀个状态机,这个你可以看下你这段代码在il中的实现),执⾏完后就会从对应的task展开(unwarp)拿到原始结果(⽐如你代码中⼏个await的地⽅),这⾥额外就可以回答你第Task和async await的差异,async await的表现是基于Task,但显式的Task会根据TaskScheduler启动线程,⽽async await不会额外新起线程,async await会从当前可⽤线程中空闲的线程来执⾏,由于所有线程都没闲着(没有所
谓的等待,特别是耗时的io等待),因此服务的吞吐量会⾼很多(适⽤于⾼io场景)
其实上⾯也解释了多线程和async await的差异了,多线程不等同于异步,你拿TaskFactory或者ThreadPool搞⼀堆线程,它们都做着同步的⼯作还是会在执⾏的时候阻塞,线程⼤量的时间就这样⽩⽩浪费在了等待响应上了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。