异步:(十)异步-什么是异步(1)

一、什么是异步

启动程序时,系统会在内存中创建一个新的进程。

进程:构成运行程序的资源的集合。这些资源包括虚地址空间、文件句柄和程序运行所需的其他许多东西。

在进程内部,系统创建了一个称为线程的内核对象,它代表了真正执行的程序。(线程是“执行线程”的简称。)一旦进程建立,系统会在 Main 方法的第一行语句处开始线程的执行。

关于线程:

  • 默认情况下,一个进程只包含一个线程(主线程),从程序的开始一直执行到结束。
  • 线程可以派生其他线程(子线程),因此在任意时刻,一个进程都可能包含不同状态的多个线程,它们执行程序的不同部分。
  • 如果一个进程拥有多个线程,它们将共享进程的资源。
  • 系统为处理器执行所调度的单元是线程,不是进程。

不使用异步的示例:
在我的电脑上,书上的示例中 CountCharacters(1, “htttp://microsoft.com” ); 里调用 DownloadString 方法会卡很久,可能传入的这个微软官方网的服务器有问题。(经我经验所知,微软官方网在某些时候就调用加载这一步时常会变得很慢,尤其登录微软账号的时候,显得更明显)于是我就直接换了个百度首页的地址来测试。

class MyDownloadString { Stopwatch sw = new Stopwatch(); const int LargeNumber = 6000000; public void DoRun() { sw.Start(); int t1 = CountCharacters(1, "https://www.baidu.com/"); int t2 = CountCharacters(2, "http://www.illustratedcsharp.com"); CountToALargeNumber(1); CountToALargeNumber(2); CountToALargeNumber(3); CountToALargeNumber(4); Console.WriteLine($"Chars in https://www.baidu.com/ :{t1}"); Console.WriteLine($"Chars in http://www.illustractedcsharp.com :{t2}"); } //获取网页源代码内容的长度 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; } //每间隔一次时间,打印结果。 //该方法在书上是有两个形参的, //但是我觉得有些多余(可能作者为了使代码规范化吧) //我为了简便就只使用一个形参 private void CountToALargeNumber(int id) { //循环作用:为了让程序执行一段时间,才打印一次结果,以方便查看计数值 //如果不执行循环,以下打印结果很可能都是相同值。 for (long i = 0; i < LargeNumber; i++) ; Console.WriteLine(" End counting {0} : {1, 4:N0}ms", id, sw.Elapsed.TotalMilliseconds); } } class Program { static void Main(string[] args) { MyDownloadString ds = new MyDownloadString(); ds.DoRun(); Console.ReadKey(); } }

输出结果:

Starting call 1 : 1 ms
Call 1 completed: 694ms
Starting call 2 : 694 ms
Call 2 completed: 3,147ms
End counting 1 : 3,188ms
End counting 2 : 3,235ms
End counting 3 : 3,280ms
End counting 4 : 3,323ms
Chars in https://www.baidu.com/ :9269
Chars in http://www.illustractedcsharp.com :5164

由输出结果可知,执行 DoRun 方法时,该方法里的代码都是按照顺序来执行的。也就是说,每调用一行代码之前,都得要等待上一行代码执行完毕之后才能执行。这里的示例中,由于 调用 DownloadString 方法是需要请求网络时间的, 所以需要等待 DownloadString 方法执行完毕之后,才能执行下一行代码。

使用异步的示例

根据上面的示例,把CountCharacters 方法重写为异步调用的方法:
把 DownloadString 改为 DownloadStringTaskAsync,即改为异步调用方法。

class MyDownloadString { Stopwatch sw = new Stopwatch(); const int LargeNumber = 6000000; public void DoRun() { sw.Start(); //此处代码修改 Task<int> t1 = CountCharacters(1, "https://www.baidu.com/"); Task<int> t2 = CountCharacters(2, "http://www.illustratedcsharp.com"); CountToALargeNumber(1); CountToALargeNumber(2); CountToALargeNumber(3); CountToALargeNumber(4); Console.WriteLine($"Chars in https://www.baidu.com/ :{t1.Result}");//此处代码修改 Console.WriteLine($"Chars in http://www.illustractedcsharp.com :{t2.Result}");//此处代码修改 } //获取网页源代码内容的长度,此处代码修改 private async Task<int> CountCharacters(int id, string uriString) { WebClient wc1 = new WebClient(); //执行此处记录下时间值 Console.WriteLine("Starting call {0} : {1, 4:N0} ms", id, sw.Elapsed.TotalMilliseconds); //需要时间下载并获取网页源代码 var result = await wc1.DownloadStringTaskAsync(new Uri(uriString));//此处代码修改 wc1.DownloadString(new Uri(uriString)); Console.WriteLine(" Call {0} completed: {1, 4:N0}ms", id, sw.Elapsed.TotalMilliseconds); return result.Length; } //每间隔一次时间,打印结果 private void CountToALargeNumber(int id) { //循环作用:为了让程序执行一段四件,才打印一次结果,以方便查看计数值 //如果不执行循环,以下打印结果很可能都是相同值。 for (long i = 0; i < LargeNumber; i++) ; Console.WriteLine(" End counting {0} : {1, 4:N0}ms", id, sw.Elapsed.TotalMilliseconds); } } class Program { static void Main(string[] args) { MyDownloadString ds = new MyDownloadString(); ds.DoRun(); Console.ReadKey(); } }

输出结果:

Starting call 1 : 6 ms
Starting call 2 : 237 ms
End counting 1 : 287ms
End counting 2 : 342ms
Call 1 completed: 384ms
End counting 3 : 394ms
End counting 4 : 440ms
Chars in https://www.baidu.com/ :9269
Call 2 completed: 925ms
Chars in http://www.illustractedcsharp.com :5164

由输出结果可知,程序执行到 DownloadStringTaskAsync 方法时,程序则以异步调用的方式进行。这样可以执行 DownloadStringTaskAsync 的同时,又可以执行下一行代码。

但是,最后两行代码,输出的参数从 t1 改为 t1.Result (从 t2 改为 t2.Result),由于Result 为 Task<int> 类型的,所以这两行代码需要等待 DownloadStringTaskAsync执行完毕后,返回其值才能被输出。如果该方法没有执行完毕,则阻塞并等待期值到来。

以上例子使用了异步调用,但是没有开辟新线程。是因为程序主线程异步调用了 DownloadStringTaskAsync 告诉系统它想要获取资源后,然后继续做自己的事情了。如果中间过程中,在后台系统请求网络得出结果后,会告诉主线程并把结果返回给它,它就停止手上的工作继续处理这个事情,处理完后再继续执行之前手上的工作。

由以上两个示例的输出结果可知,如果方法里有需要等待的代码块,那么该方法为同步方法时就会比作为异步方法时,需要耗费的时间就比较长。
(不使用异步示例中,该方法执行的整个过程所耗费的时间为3,323ms;而使用异步后,需要时间为925ms。主要差距的原因就在于,同步方法等待资源时,就会停止手上的工作,直到有结果返回时,才能继续工作。而异步方法等待资源的同时,还继续处理其他事情。)

二、async/await 特性的结构

同步方法:如果某一个程序调用某个方法,并在等待方法执行所有处理后才继续执行。
异步方法:在完成其所有方法之前就返回到调用方法。
特性:

  • 调用方法:该方法调用异步方法,然后在异步方法执行其任务的时候继续执行(可能在相同的线程上,也可能在不同的线程上)。
  • 异步方法:该方法异步执行其工作,然后立即返回到调用方法。
  • awaite 表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法可以包含任意多个 await 表达式,不过如果一个都不包含的话编译器会发出警告。(大概警告:如果不使用 awaite 表达式,则会视为同步执行。)

三、async/await 特性的整体结构

//调用方法static void Main(){Task<int> value DoAsyncStuff.CalculateSumAsync(5,6);}//异步方法staic class DoAsyncStuff{public static async Task<int> CalculateSumAsync(int i1,int i2){int sum = await TaskEx.Run(() => GetSum(i1,i2));return sum;}}

相关推荐

相关文章