一、什么是计算机进程和线程
1.1 线程知识
1.2 为什么需要异步
二、同步案例
三、异步案例
四、async/await特性的结构
// 博客是为了本人学习记录所用,基本内容都是摘抄或者思考后总结的内容,只是为了存起来自己理解。请各位多多指教,共同进步。
// 引用声明:本文部分内容及图片参考自:Solis, D.M., Illustrated C# 2012(Fourth Edition). 2013: 人民邮电出版社.
// 摘抄声明:本文部分内容引用自:http://www.cnblogs.com/liqingwen/p/5831951.html,如需引用本文也必须引用该博客。
启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序资源的集合。这些资源包括虚地址空间、文件句柄和许多其他程序所运行的东西(这个了解即可,深入可以去看计算机操作系统原理)。
在进程内部,系统创建了一个称为线程的内核对象,它代表的是真正的执行程序(线程是“执行线程”的简称)。一旦线程建立,系统会在 Main 方法的第一行语句就开始线程的执行。
1、默认情况下,一个进程只包含一个线程,从程序的开始到执行结束;
2、一个进程中的多个线程,将共享该进程的资源;
3、线程可以派生自其它线程,所以一个进程可以包含不同状态的多个线程,来执行程序的不同部分;
4、系统为处理器执行所规划的单元是线程,而不是进程。
很多情况下,我们经常能听到“高性能”、“多线程”等名词,那么为什么需要多线程呢?
在一般情况下,我们写的控制台程序都只使用了一个线程,从第一条语句按顺序执行到最后一条。但在很多的情况下,这种简单的模型会在性能或用户体验度上导致让人难以忍受的行为。例如:服务器要同时处理来自多个客户端程序的请求,又要等待数据库和其它设备的响应,这严重的削弱了性能。程序不应该将时间浪费在等待网络或者其他计算机的响应上,我们需要的是程序在等待的同时执行其它任务,这就需要异步编程。
在异步程序中,代码不需要按照编写时的顺序严格执行。有时候需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好地利用单个线程的能力,我们需要改变代码的执行顺序。这需就要用到C#5.0中构建异步程序的新特性 — async / await 。
首先,先放一个顺序执行的案例(也称为同步案例)。
/*
* 时间:2019年1月24日11点05分
* 功能:同步示例
*/
using System;
using System.Net;
using System.Diagnostics;
class MyDownloadString
{
//Stopwatch类对象提供一组方法和属性,可用于准确地测量代码中不同任务的运行时间
Stopwatch sw = new Stopwatch();
///
/// MyDownloadString类的主要执行函数
///
public void DoRun()
{
const int LargeNumber = 6000000;
sw.Start();
int t1 = CountCharacters(1, "http://www.microsoft.com");
int t2 = CountCharacters(2, "http://www.illustratedcsharp.com");
CountToALargeNumber(1, LargeNumber);
CountToALargeNumber(2, LargeNumber);
CountToALargeNumber(3, LargeNumber);
CountToALargeNumber(4, LargeNumber);
Console.WriteLine("Chars in http://www.microsoft.com :{0}", t1);
Console.WriteLine("Chars in http://www.illusratedcsharp.com :{0}", t2);
}
///
/// 函数功能:下载某网站内容,并返回该网站包含的字节数
///
/// id
/// 网站地址字符串
///
private int CountCharacters(int id, string uriString)
{
WebClient wc1 = new WebClient();
Console.WriteLine("Starting call {0} : {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
string result = wc1.DownloadString(new Uri(uriString));
Console.WriteLine(" Call {0} completed: {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
return result.Length;
}
///
/// 该方法仅执行一个消耗一定时间的任务,并循环指定的次数
///
/// id
/// 指定的循环次数
private void CountToALargeNumber(int id, int value)
{
for (long i = 0; i < value; i++)
{
;
}
Console.WriteLine(" End counting {0} : {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
}
}
class program
{
static void Main()
{
MyDownloadString ds = new MyDownloadString();
ds.DoRun();
Console.ReadKey();
}
}
/* 程序运行结果
------------------------------------------------------
Starting call 1 : 1 ms
Call 1 completed: 178 ms
Starting call 2 : 178 ms
Call 2 completed: 504 ms
End counting 1 : 523 ms
End counting 2 : 542 ms
End counting 3 : 561 ms
End counting 4 : 579 ms
Chars in http://www.microsoft.com :1020
Chars in http://www.illusratedcsharp.com :5164
------------------------------------------------------
*/
这是任务执行时间的时间轴:
由上面同步的例子可知,我们完成所有的工作都是在主线程上进行的,Call1和Call2占用了大部分时间,不管哪次调用,绝大部分时间都是浪费在等待网站的响应上。因此我们可以想到:
如果上面的例子我们能初始化两个CountCharacter调用,无需等待结果,而是直接执行4个CounterToALargeNumber调用,然后在两个CountCharacter方法调用结束后再获取结果,就可以显著提升性能。C#的async/await就允许我们这么做,因此我们可以使用该特性将上面的案例改为以下异步的案例。
运用异步的特性重写以上代码,需要说明的是下面的代码也是在主线程中完成的,没有创建任何额外的线程。
/*
* 时间:2019年1月24日12点13分
* 功能:异步案例
*/
using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
class MyDownloadString
{
//Stopwatch类对象提供一组方法和属性,可用于准确地测量代码中不同任务的运行时间
Stopwatch sw = new Stopwatch();
///
/// MyDownloadString类的主要执行函数
///
public void DoRun()
{
const int LargeNumber = 6000000;
sw.Start();
/*
* 同步案例中的代码
* int t1 = CountCharacters(1, "http://www.microsoft.com");
* int t2 = CountCharacters(2, "http://www.illustratedcsharp.com");
*/
//修改为异步(Task为保存结果的对象)
Task t1 = CountCharactersAsync(1, "http://www.microsoft.com");
Task t2 = CountCharactersAsync(2, "http://www.illustratedcsharp.com");
CountToALargeNumber(1, LargeNumber);
CountToALargeNumber(2, LargeNumber);
CountToALargeNumber(3, LargeNumber);
CountToALargeNumber(4, LargeNumber);
/*
* 同步案例中的代码
* Console.WriteLine("Chars in http://www.microsoft.com :{0}", t1);
* Console.WriteLine("Chars in http://www.illusratedcsharp.com :{0}", t2);
*/
//修改为异步(t1.Result获取结果)
Console.WriteLine("Chars in http://www.microsoft.com :{0}", t1.Result);
Console.WriteLine("Chars in http://www.illusratedcsharp.com :{0}", t2.Result);
}
///
/// 函数功能:下载某网站内容,并返回该网站包含的字节数
///
/// Task表示正在执行的工作,最终将返回int
/// id
/// 网站地址字符串
///
private async Task CountCharactersAsync(int id, string site)
{
WebClient wc1 = new WebClient();
Console.WriteLine("Starting call {0} : {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
string result = await wc1.DownloadStringTaskAsync(new Uri(site));
Console.WriteLine(" Call {0} completed: {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
return result.Length;
}
///
/// 该方法仅执行一个消耗一定时间的任务,并循环指定的次数
///
/// id
/// 指定的循环次数
private void CountToALargeNumber(int id, int value)
{
for (long i = 0; i < value; i++)
{
;
}
Console.WriteLine(" End counting {0} : {1, 4:N0} ms",
id, sw.Elapsed.TotalMilliseconds);
}
}
class program
{
static void Main()
{
MyDownloadString ds = new MyDownloadString();
ds.DoRun();
Console.ReadKey();
}
}
/* 程序运行结果
------------------------------------------------------
Starting call 1 : 4 ms
Starting call 2 : 60 ms
End counting 1 : 71 ms
End counting 2 : 81 ms
End counting 3 : 91 ms
End counting 4 : 100 ms
Call 1 completed: 152 ms
Chars in http://www.microsoft.com :1020
Call 2 completed: 1,066 ms
Chars in http://www.illusratedcsharp.com :5164
------------------------------------------------------
*/
在实现的过程中看到了几个博客:
1、走进异步编程的世界 http://www.cnblogs.com/liqingwen/p/5831951.html
2、C#异步编程(一)https://blog.csdn.net/realjh/article/details/80717746
回首刚才用到的名词:
1、同步方法:一个程序调用某个方法,等到其执行完所有处理后才继续进行下一步操作,我们就称这个方法是同步的方法,这也是我们在编程的时候默认的形式。
2、异步方法:一个程序调用某个方法,在处理完成之前就返回该方法。通过C#中的 async/await 特性就可以创建并使用异步方法。
3、async/await 组成
(1) 调用方法:该方法调用异步方法,然后在异步方法(可能在相同的线程,或者不同的线程)执行其任务的时候继续执行;
(2) 异步方法:该方法异步执行工作,然后立刻返回到调用方法;
(3) await 表达式:用于异步方法内部,必须指明需要异步执行的任务。一个异步方法可以包含多个 await 表达式(且必须至少含有一个 await 表达式,否则 IDE 会发出警告)。