C#异步编程(一) -- 基础知识及实例

目录

一、什么是计算机进程和线程

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.1 线程知识

1、默认情况下,一个进程只包含一个线程,从程序的开始到执行结束;

2、一个进程中的多个线程,将共享该进程的资源;

3、线程可以派生自其它线程,所以一个进程可以包含不同状态的多个线程,来执行程序的不同部分;

4、系统为处理器执行所规划的单元是线程而不是进程

1.2 为什么需要异步

很多情况下,我们经常能听到“高性能”、“多线程”等名词,那么为什么需要多线程呢?

在一般情况下,我们写的控制台程序都只使用了一个线程,从第一条语句按顺序执行到最后一条。但在很多的情况下,这种简单的模型会在性能或用户体验度上导致让人难以忍受的行为。例如:服务器要同时处理来自多个客户端程序的请求,又要等待数据库和其它设备的响应,这严重的削弱了性能。程序不应该将时间浪费在等待网络或者其他计算机的响应上,我们需要的是程序在等待的同时执行其它任务,这就需要异步编程。

在异步程序中,代码不需要按照编写时的顺序严格执行。有时候需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好地利用单个线程的能力,我们需要改变代码的执行顺序。这需就要用到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();

    /// <summary>
    /// MyDownloadString类的主要执行函数
    /// </summary>
    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);
    }

    /// <summary>
    /// 函数功能:下载某网站内容,并返回该网站包含的字节数
    /// </summary>
    /// <param name="id">id</param>
    /// <param name="uriString">网站地址字符串</param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 该方法仅执行一个消耗一定时间的任务,并循环指定的次数
    /// </summary>
    /// <param name="id">id</param>
    /// <param name="value">指定的循环次数</param>
    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();

    /// <summary>
    /// MyDownloadString类的主要执行函数
    /// </summary>
    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<int>为保存结果的对象)
        Task<int> t1 = CountCharactersAsync(1, "http://www.microsoft.com");
        Task<int> 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);
    }

    /// <summary>
    /// 函数功能:下载某网站内容,并返回该网站包含的字节数
    /// </summary>
    /// Task<int>表示正在执行的工作,最终将返回int
    /// <param name="id">id</param>
    /// <param name="site">网站地址字符串</param>
    /// <returns></returns>
    private async Task<int> 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;
    }

    /// <summary>
    /// 该方法仅执行一个消耗一定时间的任务,并循环指定的次数
    /// </summary>
    /// <param name="id">id</param>
    /// <param name="value">指定的循环次数</param>
    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

 

四、async/await特性的结构

回首刚才用到的名词:

1、同步方法:一个程序调用某个方法,等到其执行完所有处理后才继续进行下一步操作,我们就称这个方法是同步的方法,这也是我们在编程的时候默认的形式。

2、异步方法:一个程序调用某个方法,在处理完成之前就返回该方法。通过C#中的 async/await 特性就可以创建并使用异步方法。

3、async/await 组成

    (1) 调用方法:该方法调用异步方法,然后在异步方法(可能在相同的线程,或者不同的线程)执行其任务的时候继续执行;

    (2) 异步方法:该方法异步执行工作,然后立刻返回到调用方法;

    (3) await 表达式:用于异步方法内部,必须指明需要异步执行的任务。一个异步方法可以包含多个 await 表达式(且必须至少含有一个 await 表达式,否则 IDE 会发出警告)。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值