c# 异步编程入门

一、什么算异步?

  广义来讲,两个工作流能同时进行就算异步,例如,CPU与外设之间的工作流就是异步的。在面向服务的系统中,各个子系统之间通信一般都是异步的,例如,订单系统与支付系统之间的通信是异步的,又如,在现实生活中,你去馆子吃饭,工作流是这样的,点菜->下单->做你的事->上菜->吃饭,这个也是异步的,具体来讲你和厨师之间是异步的,异步是如此重要,因外它代表者高效率(两者或两者以上的工作可以同时进行),但复杂,同步的世界简单,但效率极极低。

二、在编程中的异步

  在编程中,除了同步和异步这两个名词,还多了一个阻塞和非阻塞,其中,阻塞和非阻塞是针对线程的概念,那么同步和异步是针对谁呢?其实很多情况下同步和异步并没有具体针对某一事物,所以导致了针对同步阻塞、同步非阻塞、异步阻塞、异步非阻塞这几个概念的模糊不清。并且也确实没有清晰的边界,请看以下例子:

  public static void DoWorkA()
  {
    Thread thread = new Thread(() =>
    {
      Console.WriteLine("WorkA Done!");
    });
    thread.Start();
  }

  public static void DoWordB()
  {
    Thread thread = new Thread(() =>
    {
      Console.WriteLine("WorkB Done!");
    });
    thread.Start();
  }
  static void Main(string[] args)
  {
    DoWorkA();
    DoWordB();
  }

  假设运行该代码的CPU是单核单线程,那么请问?DoWorkA()、DoWorkB()这两个函数是异步的吗?因为CPU是单核,所以根本不能同时运行两个函数,那么从这个层次来讲,他们之间其实是同步的,但是,现实的情况是我们一般都认为他们之间是异步的,因为我们是从代码的执行顺序角度考虑的,而不是从CPU本身的工作流程考虑的。所以要分上下文考虑。再请看下面这个例子:

  static void Main(string[] args)
  {
    DoWorkA();
    QueryDataBaseSync();//同步查询数据库
    DoWorkB();
  }

  从代码的执行顺序角度考虑,这三个函数执行就是同步的,但是,从CPU的角度来讲,数据库查询工作(另一台机器)和CPU计算工作是异步的,在下文中,没有做特别申明,则都是从代码的执行顺序角度来讨论同步和异步。
  再解释一下阻塞和非阻塞以及相关的知识:

  阻塞特指线程由运行状态转换到挂起状态,但CPU并不会阻塞,操作系统会切换另一个处于就绪状态的线程,并转换成运行状态。导致线程被阻塞的原因有很多,如:发生系统调用(应用程序调用系统API,如果调用成功,会发生从应用态->内核态->应用态的转换开销),但此时外部条件并没有满足,如从Socket内核缓冲区读数据,此时缓冲区还没有数据,则会导致操作系统挂起该线程,切换到另一个处于就绪态的线程然后给CPU执行,这是主动调用导致的,还有被动导致的,对于现在的分时操作系统,在一个线程时间片到了之后,会发生时钟中断信号,然后由操作系统预先写好的中断函数处理,再按一定策略(如线程优先级)切换至另一个线程执行,导致线程被动地从运行态转换成挂起状态。
  非阻塞一般指函数调用不会导致执行该函数的线程从运行态转换成挂起状态。

三、原始的异步编程模式之回调函数#

  在此之前,我们先稍微了解下图形界面的工作原理,GUI程序大概可以用以下伪代码表示:

While(GetMessage() != 'exit') //从线程消息队列中获取一个消息,线程消息队列由系统维护,例如鼠标移动事件,这个事件由操作系统捕捉,并投递到线程的消息队列中。
{
  msg = TranslateMessage();//转换消息格式
  DispatherMessage(msg);//分发消息到相应的处理函数
}

  其中DispatherMessage根据不同的消息类型,调用不同的消息处理函数,例如鼠标移动消息(MouseMove),此时消息处理函数可以根据MouseMove消息中的值,做相应的处理,例如调用绘图相关函数画出鼠标此刻的形状。
  一般来讲,我们称这个循环为消息循环(事件循环、EventLoop),编程模型称为消息驱动模型(事件驱动),在UI程序中,执行这部分代码的线程一般只有一个线程,称为UI线程,为什么是单线程,读者可以去思考。
  以上为背景知识。现在,我们思考,假如在UI线程中执行一个会导致UI线程被阻塞的操作,或者在UI线程执行一个纯CPU计算的工作,会发生什么样的结果?如果执行一个导致UI线程被阻塞的操作,那么这个消息循环就会被迫停止,导致相关的绘图消息不能被相应的消息处理函数处理,表现就是UI界面“假死”,直到UI线程被唤起。如果是纯CPU计算的工作,那么也会导致其他消息不能被及时处理,也会导致界面“假死”现象。如何处理这种情况?写异步代码。
  我们先用控制台程序模拟这个UI程序,后面以此为基础。

  public static string GetMessage()
  {
    return Console.ReadLine();
  }

  public static string TranslateMessage(string msg)
  {
    return msg;
  }

  public static void DispatherMessage(string msg)
  {
    switch (msg)
    {
      case "MOUSE_MOVE":
        {
          OnMOUSE_MOVE(msg);
          break;
        }
      default:
        break;
    }
  }

  public static void OnMOUSE_MOVE(string msg)
  {
    Console.WriteLine("开始绘制鼠标形状");
  }

  static void Main(string[] args)
  {
    while(true)
    {
      string msg = GetMessage();
      if (msg == "quit") return;
      string m = TranslateMessage(msg);
      DispatherMessage(m);
    }
  }

1、回调函数

  上面那个例子,一但外部有消息到来,根据不同的消息类型,调用不同的处理函数,如鼠标移动时产生MOUSE_DOWN消息,相应的消息处理函数就开始重新绘制鼠标的形状,这样一但你鼠标移动,就你会发现屏幕上的鼠标跟着移动了。
  现在假设我们增加一个消息处理函数,如OnMOUSE_DOWN,这个函数内部进行了一个阻塞的操作,如发起一个HTTP请求,在HTTP请求回复到来前,该UI程序会“假死”,我们编写异步代码来解决这个问题。

  public static int Http()
  {
    Thread.Sleep(1000);//模拟网络IO延时
    return 1;
  }
  public static void HttpAsync(Action<int> action,Action error)
  {
    //这里我们用另一个线程来实现异步IO,由于Http方法内部是通过Sleep来模拟网络IO延时的,这里也只能通过另一个线程来实现异步IO
    //但记住,多线程是实现异步IO的一个手段而已,它不是必须的,后面会讲到如何通过一个线程来实现异步IO。
    Thread thread = new Thread(() =>
    {
      try
      {
        int res = Http();
        action(res);
      }
      catch
      {
        error();
      }

    });

    thread.Start();
  }
  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync(res =>
    {
      Console.WriteLine("请求成功!");
      //使用该结果做一些工作
    }, () =>
    {
      Console.WriteLine("请求发生错误!");
    });
  }

  此时界面不再“假死”了,我们看下代码可读性,感觉还行,但是,如果再在回调函数里面再发起类似的异步请求呢?(有人可能有疑问,为什么还需要发起异步请求,我发同步请求不行吗?这都是在另一个线程里了。是的,在这个例子里是没问题的,但真实情况是,执行回调函数的代码,一般都会在UI线程,因为取得结果后需要更新相关UI组件上的界面,例如文字,而更新界面的操作都是放在UI线程里的,如何把回调函数放到UI线程上执行,这里不做讨论,在.NET中,这跟同步上下文(Synchronization context)有关,后面会讲到),那么代码会变成这样

  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync(res =>
    {
      Console.WriteLine("请求成功!");
      //使用该结果做一些工作

      HttpAsync(r1 =>
      {
        //使用该结果做一些工作

        HttpAsync(r2 =>
        {
          //使用该结果做一些工作
        }, () =>
        {

        });
      }, () =>
      {

      });
    }, () =>
    {
      Console.WriteLine("请求发生错误!");
    });
  }

  写过JS的同学可能很清楚,这叫做“回调地狱”,如何解决这个问题?JS中有Promise,而C#中有Task,我们先用Task来写这一段代码,然后自己实现一个与Task功能差不多的简单的类库。

  public static Task<int> HttpAsync()
  {
    return Task.Run(() =>
    {
      return Http();
    });
  }

  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync()
      .ContinueWith(t =>
      {
        if(t.Status == TaskStatus.Faulted)
        {

        }else if(t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      })
      .ContinueWith(t =>
      {
        if (t.Status == TaskStatus.Faulted)
        {

        }
        else if (t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      })
      .ContinueWith(t =>
      {
        if (t.Status == TaskStatus.Faulted)
        {

        }
        else if (t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      });
  }

  是不是感觉清爽了许多?这是编写异步代码的第一个跃进。下篇将会介绍,如何自己实现一个简单的Task。后面还会提到C#中async/await的本质作用,async/await是怎么跟Task联系起来的,怎么把自己写的Task库与async/await连结起来,以及一个线程如何实现异步IO。
  觉得有收获的不妨点个赞,有支持才有动力写出更好的文章。

以上就是c# 异步编程入门的详细内容,更多关于c# 异步编程的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#中异步Socket通信编程代码实例

    本文将在C#中Socket同步通信的基础上,分析和研究Socket异步编程的实现方法,目的是深入了解Socket编程的基本原理,增强对网络游戏开发相关内容的认识. 什么是Socket编程的异步是实现 所谓Socket编程的异步实现是指按照异步过程来实现Socket编程,那么什么是异步过程呢,我们把在完成了一次调用后通过状态.通知和回调来告知调用者的方式成为异步过程,换句话说,在异步过程中当调用一个方法时,调用者并不能够立刻得到结果,只有当这个方法调用完毕后调用者才能获得调用结果.这样做的好处是什

  • C#异步编程几点需要注意的地方

    尽量不要编写返回值类型为void的异步方法 在通常情况下,建议大家不要编写那种返回值类型为void的异步方法,因为这样做会破坏该方法的启动者与方法本身之间的约定,这套约定本来可以确保主调方能够捕获到异步方法所发生的异常. 正常的异步方法是通过它返回的Task对象来汇报异常的.如果执行过程中发生了异常,那么Task对象就进入了faulted(故障)状态.主调方在对异步方法所返回的Task对象做await操作时,该对象若已处在faulted状态,系统则会将执行异步方法的过程中所发生的异常抛出,反之,

  • 基于C#的socket编程的TCP异步的实现代码

    一.摘要 本篇阐述基于TCP通信协议的异步实现. 二.实验平台 Visual Studio 2010 三.异步通信实现原理及常用方法 3.1 建立连接 在同步模式中,在服务器上使用Accept方法接入连接请求,而在客户端则使用Connect方法来连接服务器.相对地,在异步模式下,服务器可以使用BeginAccept方法和EndAccept方法来完成连接到客户端的任务,在客户端则通过BeginConnect方法和EndConnect方法来实现与服务器的连接. BeginAccept在异步方式下传入

  • c# 基于任务的异步编程模式(TAP)

    异步编程是C#5.0的一个重要改进,提供两个关键字:async和await.使用异步编程,方法的调用是在后台运行(通常在线程或任务的帮助下),但不会阻塞调用线程.异步模式分为3种:异步模式.基于事件的异步模式和基于任务的异步模式(TAP).TAP是利用关键字async和await实现的,本文将讲解TAP模式.async和await关键字只是编译器的功能.编译器最终会用Task类创建代码. 1.创建任务 建立一个同步方法Greeting,该方法在等待一段时间后,返回一个字符串. private s

  • c# 基于任务的异步编程模式(TAP)的异常处理

    在前面讲到了<基于任务的异步编程模式(TAP)>,但是如果调用异步方法,没有等待,那么调用异步方法的线程中使用传统的try/catch块是不能捕获到异步方法中的异常.因为在异步方法执行出现异常之前,已经执行完毕. 1.没有等待的调用异步方法 ThrowAfter方法是在一定延迟后抛出一个异常: private async Task ThrowAfter(int ms,string message) { await Task.Delay(ms); Console.WriteLine("

  • C#实现异步编程的方法

    最近在我参与的几个.Net项目中都有用到异步编程,作为一名.Net小白,很有必要好好地学习一下C#异步编程. 什么是异步 异步指的就是不用阻塞当前线程来等待任务的完成,而是将任务扔到线程池中去执行,当前线程可以继续向下执行,直至其它线程将任务完成,并回调通知当前线程.整个任务从开始到结束都是异步完成的,不会阻塞当前线程.因此,异步很重要的一点就是,不会阻塞当前线程. 实现异步编程 在C#语言中,主要是通过委托来实现异步编程的,在委托类型中定义了两个方法BeginInvoke()和EndInvok

  • C#编程总结(六)详解异步编程

    1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库.异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行. 2.同步与异步的区别 同步(Synchronous):在执行某个操作时,应用程序必须等待该操作执行完成后才能继续执行. 异步(Asynchronous):在执行某个操作时,应用程序可在异步操作执行时继续执行.实质:异步操作,启动了新的线程,主线程与方法线程并行执行. 3.异

  • C#异步编程详解

    前言 本节主要介绍异步编程中Task.Async和Await的基础知识. 什么是异步? 异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程. 异步和多线程 相同点:避免调用线程阻塞,从而提高软件的可响应性. 不同点: 异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能.C#5.0 .NET4.5 以后关键字Async和Awai

  • 深入分析C#异步编程详解

    同步方法和异步方法的区别同步方法调用在程序继续执行之前需要等待同步方法执行完毕返回结果异步方法则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作异步编程概览.NET Framework 允许您异步调用任何方法.定义与您需要调用的方法具有相同签名的委托:公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法.BeginInvoke 方法用于启动异步调用.它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数(将在稍后描述)

  • 解析C#多线程编程中异步多线程的实现及线程池的使用

    0.线程的本质 线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度. 1.多线程: 使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解. 2.多线程的使用: (1)最简单.最原始的使用方法:Thread oGetArgThre

  • c# 使用异步编程的方法

    怎么使用异步,就是用委托进行处理,如果委托对象在调用列表中只有一个方法,它就可以异步执行这个方法.委托类有两个方法,叫做BeginInvoke和EndInvoke,它们是用来异步执行使用. 异步有三种模式 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续. 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情. 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成.在发起的线程中的引用方法完成之后,发起的线程

随机推荐