涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $"" 来拼接字符串,相当于string.Format() 方法。
序言
启动程序之后,系统会在内存中创建一个新的进程。进程是构成运行程序资源的集合。在进程内部,有称为线程的内核对象,它代表的是真正的执行程序。系统会在 Main 方法的第一行语句就开始线程的执行。
线程:
①默认情况,一个进程只包含一个线程,从程序的开始到执行结束;
②线程可以派生自其它线程,所以一个进程可以包含不同状态的多个线程,来执行程序的不同部分;
③一个进程中的多个线程,将共享该进程的资源;
④系统为处理器执行所规划的单元是线程,而非进程。
为什么要使用异步编程
提高程序的性能,相应多端请求或者提高响应速度,采用异步编程。在异步的程序中,代码不需要按照编写是的顺序执行。需要用到5.0引入的 async/await
来构建异步方法。
不用异步和使用异步的比较
未使用异步运行结果
使用异步运行结果
技巧类提示:
直接拼接字符串是一种比较消耗性能的手段,如果对字符串连接有性能要求的话应该使用StringBuilder方法。
代码解释
OldTest.async.await代码:
private static readonly Stopwatch stopwatch = new Stopwatch();
static void Main(string[] args)
{
stopwatch.Start();
const string strTest1 = "https://www.csdn.net/";
const string strTest2 = "https://blog.csdn.net/qq_36981814";
var strCountResult1 = CountChars(1, strTest1);
var strCountResult2 = CountChars(2, strTest2);
for (int i = 0; i < 3; i++)
{
ExtraOperation(i + 1);
}
Debug.WriteLine($"{strTest1} 的字符个数:{strCountResult1}");
Debug.WriteLine($"{strTest2} 的字符个数:{strCountResult2}");
}
private static void ExtraOperation(int id)
{
var s = "";
for (var i = 0; i < 6000; i++)
{
s += i;
}
Debug.WriteLine($"id={id}的 ExtraOperation 方法完成:{stopwatch.ElapsedMilliseconds} ms");
}
private static int CountChars(int id, string address)
{
var wc = new WebClient();
Debug.WriteLine($"开始调用 id = {id}:{stopwatch.ElapsedMilliseconds} ms");
var result = wc.DownloadString(address);
Debug.WriteLine($"调用完成 id = {id}:{stopwatch.ElapsedMilliseconds} ms");
return result.Length;
}
NewTest.async.await代码:
private static readonly Stopwatch stopwatch = new Stopwatch();
static void Main(string[] args)
{
stopwatch.Start();
const string strTest1 = "https://www.csdn.net/";
const string strTest2 = "https://blog.csdn.net/qq_36981814";
Task<int> strCountResult1 = CountCharsAsync(1, strTest1);
Task<int> strCountResult2 = CountCharsAsync(2, strTest2);
for (int i = 0; i < 3; i++)
{
ExtraOperation(i + 1);
}
Debug.WriteLine($"{strTest1} 的字符个数:{strCountResult1.Result}");
Debug.WriteLine($"{strTest2} 的字符个数:{strCountResult2.Result}");
}
private static void ExtraOperation(int id)
{
var s = "";
for (var i = 0; i < 6000; i++)
{
s += i;
}
Debug.WriteLine($"id={id}的 ExtraOperation 方法完成:{stopwatch.ElapsedMilliseconds} ms");
}
private static async Task<int> CountCharsAsync(int id, string address)
{
var wc = new WebClient();
Debug.WriteLine($"开始调用 id = {id}:{stopwatch.ElapsedMilliseconds} ms");
var result = await wc.DownloadDataTaskAsync(address);
Debug.WriteLine($"调用完成 id = {id}:{stopwatch.ElapsedMilliseconds} ms");
return result.Length;
}
①从 Main 方法执行到 CountCharactersAsync(1, url1) 方法时,该方法会立即返回,然后才会调用它内部的方法开始下载内容。该方法返回的是一个 Task<int> 类型的占位符对象,表示计划进行的工作。这个占位符最终会返回 int 类型的值。
②这样就可以不必等 CountCharactersAsync(1, url1) 方法执行完成就可以继续进行下一步操作。到执行 CountCharactersAsync(2, url2) 方法时,跟 ① 一样返回 Task<int> 对象。
③然后,Main 方法继续执行三次 ExtraOperation 方法,同时两次 CountCharactersAsync 方法依然在持续工作 。
④t1.Result 和 t2.Result 是指从 CountCharactersAsync 方法调用的 Task<int> 对象取结果,如果还没有结果的话,将阻塞,直有结果返回为止。
asynic/await结构
同步方法:一个程序调用某个方法,等到其执行完成之后才进行下一步操作。这也是默认的形式。
异步方法:一个程序调用某个方法,在处理完成之前就返回该方法。通过 async/await 我们就可以实现这种类型的方法。
async/await 结构可分成三部分:
(1)调用方法:该方法调用异步方法,然后在异步方法执行其任务的时候继续执行;
(2)异步方法:该方法异步执行工作,然后立刻返回到调用方法;
(3)await 表达式:用于异步方法内部,指出需要异步执行的任务。一个异步方法可以包含多个 await 表达式(不存在 await 表达式的话 IDE 会发出警告)。
异步方法详解
异步方法:在执行完成前立即返回调用方法,在调用方法继续执行的过程中完成任务。
语法分析:
(1)关键字:方法头使用 async 修饰。
(2)要求:包含 N(N>0) 个 await 表达式(不存在 await 表达式的话 IDE 会发出警告),表示需要异步执行的任务。
(3)返回类型:只能返回 3 种类型(void、Task 和 Task<T>)。Task 和 Task<T> 标识返回的对象会在将来完成工作,表示调用方法和异步方法可以继续执行。
(4)参数:数量不限,但不能使用 out 和 ref 关键字。
(5)命名约定:方法后缀名应以 Async 结尾。
(6)其它:匿名方法和 Lambda 表达式也可以作为异步对象;async 是一个上下文关键字;关键字 async 必须在返回类型前。
异步方法简单结构图