C#异步编程由浅入深(一)

目录
  • 一、什么算异步?
  • 二、在编程中的异步
  • 三、原始的异步编程模式之回调函数
    • 1、回调函数

一、什么算异步?

广义来讲,两个工作流能同时进行就算异步,例如,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!");
    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)
                    //做一些工作
                }
            })
                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#异步编程详解

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

  • C#异步编程由浅入深(二)之Async/Await的使用

      考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理.实现一个类Task的库则放在后面讲.首先回顾一下上篇博客的场景. class Program { public static string GetMessage() { return Console.ReadLine(); } public static string TranslateMessage(string msg) return m

  • C#异步编程由浅入深(三)之详解Awaiter

      上一篇末尾提到了Awaiter这个类型,上一篇说了,能await的对象,必须包含GetAwaiter()方法,不清楚的朋友可以看上篇文章.那么,Awaiter到底有什么特别之处呢?  首先,从上篇文章我们知道,一个Awaiter必须实现INotifyCompletion接口,这个接口定义如下: namespace System.Runtime.CompilerServices { /// <summary> /// Represents an operation that will sch

  • c# 异步编程基础讲解

    现代应用程序广泛使用文件和网络 I/O.I/O 相关 API 传统上默认是阻塞的,导致用户体验和硬件利用率不佳,此类问题的学习和编码的难度也较大.而今基于 Task 的异步 API 和语言级异步编程模式颠覆了传统模式,使得异步编程非常简单,几乎没有新的概念需要学习. 异步代码有如下特点: 在等待 I/O 请求返回的过程中,通过让出线程来处理更多的服务器请求. 通过在等待 I/O 请求时让出线程进行 UI 交互,并将长期运行的工作过渡到其他 CPU,使用户界面的响应性更强. 许多较新的 .NET

  • c# 异步编程入门

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

  • C#异步编程由浅入深(一)

    目录 一.什么算异步? 二.在编程中的异步 三.原始的异步编程模式之回调函数 1.回调函数 一.什么算异步? 广义来讲,两个工作流能同时进行就算异步,例如,CPU与外设之间的工作流就是异步的.在面向服务的系统中,各个子系统之间通信一般都是异步的,例如,订单系统与支付系统之间的通信是异步的,又如,在现实生活中,你去馆子吃饭,工作流是这样的,点菜->下单->做你的事->上菜->吃饭,这个也是异步的,具体来讲你和厨师之间是异步的,异步是如此重要,因外它代表者高效率(两者或两者以上的工作可

  • 详解JavaScript异步编程中jQuery的promise对象的作用

    Promise, 中文可以理解为愿望,代表单个操作完成的最终结果.一个Promise拥有三种状态:分别是unfulfilled(未满足的).fulfilled(满足的).failed(失败的),fulfilled状态和failed状态都可以被监听.一个愿望可以从未满足状态变为满足或者失败状态,一旦一个愿望处于满足或者失败状态,其状态将不可再变化.这种"不可改变"的特性对于一个Promise来说非常的重要,它可以避免Promise的状态监听器修改一个Promise的状态导致别的监听器的行

  • 浅谈Node异步编程的机制

    本文介绍了Node异步编程,分享给大家,具体如下: 目前的异步编程主要解决方案有: 事件发布/订阅模式 Promise/Deferred模式 流程控制库 事件发布/订阅模式 Node自身提供了events模块,可以轻松实现事件的发布/订阅 //订阅 emmiter.on("event1",function(message){ console.log(message); }) //发布 emmiter.emit("event1","I am mesaage!

  • 再谈JavaScript异步编程

    随着前端的发展,异步这个词真是越来越常见了.假设我们现在有这么一个异步任务: 向服务器发起数次请求,每次请求的结果作为下次请求的参数. 来看看我们都有哪些处理方法: Callbacks 最先想到也是最常用的便是回调函数了,我们来进行简单的封装: let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result =

  • Python的Tornado框架异步编程入门实例

    Tornado Tornado 是一款非阻塞可扩展的使用Python编写的web服务器和Python Web框架, 可以使用Tornado编写Web程序并不依赖任何web服务器直接提供高效的web服务.所以Tornado不仅仅是一个web框架而且还是一款可以用于生产环境的高效的web服务器 Torando 在Linux和FreeBSD上使用高效的异步I/O模型 epoll 和kqueue来实现高效的web服务器, 所以 tornado在Linux上和FreeBSD系列性能可以达到最高 接口 当然

  • .NET中的异步编程-EAP/APM使用方法及案例介绍

    从.NET 4.5开始,支持的三种异步编程模式: •基于事件的异步编程设计模式 (EAP,Event-based Asynchronous Pattern) •异步编程模型(APM,Asynchronous Programming Model) •基于任务的编程模型(TAP,Task-based Asynchronous Pattern) 基于任务的异步模式 (TAP) 是基于 System.Threading.Tasks 命名空间的 Task 和 Task<TResult>,用于表示任意异步

  • js异步编程小技巧详解

    异步回调是js的一大特性,理解好用好这个特性可以写出很高质量的代码.分享一些实际用的一些异步编程技巧. 1.我们有些应用环境是需要等待两个http请求或IO操作返回后进行后续逻辑的处理.而这种情况使用回调嵌套代码会显得很难维护,而且也没有充分使用js的异步优势. 看下实例(为了大家容易理解使用了jq作为示例) $.get("获取数据1.html",function(data,status){ $.get("获取数据2.html",function(data1,sta

  • Python的Twisted框架上手前所必须了解的异步编程思想

    前言 最近有人在Twisted邮件列表中提出诸如"为任务紧急的人提供一份Twisted介绍"的需求.值得提前透露的是,这个系列并不会如他们所愿.尤其是介绍Twisted框架和基于Python 的异步编程而言,可能短时间无法讲清楚.因此,如果你时间紧急,这恐怕不是你想找的资料. 我相信如果对异步编程模型一无所知,快速的介绍同样无法让你对其有所理解,至少你得稍微懂点基础知识吧.我已经用Twisted框架几年了,因此思考过我当初是怎么学习它(学得很慢)并发现学习它的最大难度并不在Twiste

随机推荐