C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)

异步编程的基础知识

C#5推出的async和await关键字使异步编程从表面上来说变得简单了许多,我们只需要了解不多的知识就可以编写出有效的异步代码。

在介绍async和await之前,先介绍一些基础的概念:

并发:同时做很多事情。

这个解释直接表明了并发的作用。终端用户程序利用并发功能,在输入数据库的同时响应用户输入。服务器应用利用并发,在处理第一个请求的同时响应第二个请求。只要你希望程序同时做多件事情,你就需要并发。几乎每个软件程序 都会受益于并发。

多线程:并发的一种形式,它采用多个线程来执行程序。

从字面上看,多线程就是使用多个线程。本书后续章节将介绍,多线程是并发的一种形式,但不是唯一的形式。实际上,直接使用底层线程类型在现代程序中基本不起作用。 比起老式的多线程机制,采用高级的抽象机制会让程序功能更加强大、效率更高。因此,本书将尽量不涉及一些过时的技术。书中所有多线程的方法都采用高级类型,而不是Thread或BackgroundWorker。但是,不要认为多线程已经彻底被淘汰了!因为线程池要求多线程继续存在。线程池存放任务的队列,这个队列能够根据需要自行调整。相应地,线程池产生了另一个重要的并发形式:并行处理。

并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程。

为了让处理器的利用效率最大化,并行处理(或并行编程)采用多线程。当现代多核CPU执行大量任务时,若只用一个核执行所有任务,而其他核保持空闲,这显然是不合理的。并行处理把任务分割成小块并分配给多个线程,让它们在不同的核上独立运行。并行处理是多线程的一种,而多线程是并发的一种。在现代程序中,还有一种非常重要但很多人还不熟悉的并发类型:异步编程。

异步编程:并发的一种形式,它采用future模式或回调(callback)机制,以避免产生不必要的线程。

一个future(或promise)类型代表一些即将完成的操作。在.NET中,新版future类型有Task和Task<TResult>。在老式异步编程API中,采用回调或事件(event),而不是future。异步编程的核心理念是异步操作:启动了的操作将会在一段时间后完成。这个操作正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当操作完成时,会通知它的future,或者调用回调函数,以便让程序知道操作已经结束。异步编程是一种功能强大的并发形式,但直至不久前,实现异步编程仍需要特别复杂的代码。VS2012支持async和await,这让异步编程变得几乎和同步(非并发)编程一样容易。

异步编程简介

异步编程的执行流程

现代的异步.NET程序使用两个关键字:async和await。async关键字加在方法声明上,它的主要目的是使方法内的await关键字生效(为了保持向后兼容,同时引入了这两个关键字)。如果async方法有返回值,应返回Task<T>;如果没有返回值,应返回Task。这些task类型相当于future,用来在异步方法结束时通知主程序。还有一种是返回void,这种就是存粹的为了兼容事件处理程序。所以,除了用于注册实践处理程序,不建议在别的地方使用返回void的异步代码。

先来看一个例子:

 async Task AsyncMethod()
        {
            Console.WriteLine("Sync execute before await");//①同步执行的代码
            await Task.Delay(TimeSpan.FromSeconds(5));//②异步等待,非阻塞
            Console.WriteLine("callback method");//③任务的延续
        }

和其他方法一样,async方法在开始时以同步方式执行①。在async方法内部,await关键字对它的参数执行一个异步等待②。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则,它会暂停async方法,并返回,留下一个未完成的task(token)。一段时间后,操作完成,async方法就恢复运行③。就是这么简单。编译器在后面帮助我们做了大量的工作。在后续的章节中,会详细介绍编译器的所作所为。

同步上下文:一个async方法是由多个同步执行的程序块组成的,每个同步程序块之间由await语句分隔②。第一个同步程序块在调用这个方法的线程中运行,但其他同步程序块在哪里运行呢?情况比较复杂。最常见的情况是,用await语句等待一个任务完成,当该方法在await处暂停时,就可以捕捉上下文(context)。如果当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext。如果当前SynchronizationContext为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行③。一般来说,运行UI线程时采用UI上下文,处理ASP.NET请求时采用ASP.NET请求上下文,其他很多情况下则采用线程池上下文。因此,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在UI线程中调用DoSomethingAsync,这个方法的每个同步程序块都将在此UI线程上运行。但是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。要避免这种行为,可以在await中使用ConfigureAwait方法,将参数continueOnCapturedContext设为false。接下来的代码刚开始会在调用的线程里运行,在被await暂停后,则会在线程池线程里继续运行。

可等待模式:关键字await不仅能用于Task,还能用于所有遵循特定模式的awaitable类型——在编译器生成的状态机中,有一个MoveNext的方法,在这个方法中会调用await的对象是否有一个GetAwaiter的方法,这个方法是否会返回一个awaiter,awaiter里面是否包含了GetResult方法和IsCompleted属性,awaiter遵循的接口是INotifyCompletion和ICriticalNotifyCompletion,从名字上面来看就知道他们的意思都是发送一个通知。类似的awaitable类型还有YieldAwaitable、ConfiguredTaskAwaitable,awaitable类型的一个特点是有一个GetAwaiter的方法来返回一个awaiter。

迭代器也是类似的原理,并且它出现的更早(C#2)。在foreach循环一个序列的时候,他不要求这个序列必须实现了IEnumerable或者IEnumerable<T>,foreach会寻找这个序列是否有一个GetEnumerator的方法,这个方法是否返回一个Enumerator,这个Enumerator是否包含一个MoveNext方法和一个返回当前元素的Current属性。

处理异常

因为不确定Task的执行线程,所以Task不会主动的抛出异常,在返回的Task中的Status属性上会指示Task的执行结果,Status属性是TaskStatus枚举类型,定义如下:

 public enum TaskStatus
        {
            Created,
            WaitingForActivation,
            WaitingToRun,
            Running,
            WaitingForChildrenToComplete,
            RanToCompletion,
            Canceled,
            Faulted,
        }

Task上面还有一个Exception属性,类型是一个AggregateException。正常情况下,当Task执行顺利完成时,Exception返回null。但Task执行失败时,Exception属性会返回一个AggregateException类型的异常,这个异常包含了Task执行过程中抛出的所有异常。发生异常时,任务结束,不直接抛出异常。只有在使用一个Task的时候,比如Task.Wait()、Task.WhenAll()、等等。还有,在await一个Task的时候,也会抛出异常,但是会抛出AggregateException中的第一个异常。

死锁

关于异步编程还有一个重要的准则就是,在有UI线程的地方,如果你要使用异步编程,就要异步到底,考虑下面的代码:

 async Task WaitAsync()
        {
            //这里awati会捕获当前上下文……
            await Task.Delay(TimeSpan.FromSeconds(1)); // ……这里会试图用上面捕获的上下文继续执行
        }
        void Deadlock()
        {
            //开始 延迟
            Task task = WaitAsync(); //同步程序块,正在等待异步方法完成
            task.Wait();
        }

上面的代码如果在UI线程或Asp.net 中执行,就会发生死锁,来看看到底发生了什么:这两种上下文每次只能运行一个线程。Deadlock方法调用WaitAsync方法,WaitAsync方法开始调用delay语句。然后,Deadlock方法(同步)等待WaitAsync方法完成,同时阻塞了上下文线程。当delay语句结束时,await试图在已捕获的上下文中继续运行WaitAsync方法,但这个步骤无法成功,因为上下文中已经有了一个阻塞的线程,并且这种上下文只允许同时运行一个线程。这里有两个方法可以避免死锁:在WaitAsync中使用ConfigureAwait(false)(导致await忽略该方法的上下文),或者用await语句调用WaitAsync方法(让Deadlock变成一个异步方法)。

基础的东西就这么多,这个章节里面没有多少例子,不过如果能看懂里面的所有意思,那么编写异步编程也不是什么难事。下面的章节会详细的介绍编译器为async和await所做的所有事情,并会将相关的概念进行进一步的扩展。

原文地址:https://www.cnblogs.com/pangjianxin/p/8707555.html

时间: 2024-10-10 13:15:08

C#复习笔记(5)--C#5:简化的异步编程(异步编程的基础知识)的相关文章

《高性能MySQL》读书笔记之 MySQL锁、事务、多版本并发控制的基础知识

1.2 并发控制 1.2.1 读写锁 在处理并发读或写时,通过实现一个由两种类型的锁组成的锁系统来解决问题.这两种类型的锁通常被称为 共享锁(shared lock) 和 排它锁(exclusive lock),也叫读锁(read lock)和写锁(write lock). 读锁是共享的,或者说是不互相阻塞的.多个客户端可以在同一时刻读取同一个资源,而互不干扰.写锁则是排他的,也就是说一个写锁会阻塞其他写锁和读锁. 1.2.2 锁粒度 为了提高共享资源的并发性,尽量只锁定需要修改的部分数据,而不

[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少.线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务.Java是第一个在语言层面就支持线程操作的主流编程语言.和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等.同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和

数据库系统概论 复习笔记。

大学课本复习笔记,知识点总结. 2012-08-15 数据模型中的 型,Type, 学号,姓名.... 值,Value, 0001, Ender..... 数据库系统的三级模式,两级映射, 外模式,External Schema/Sub Schema, 是用户能看得到的直接使用的.一个数据库有多个外模式,应用于用户的不同需求.但每个应用程序只能使用一个外模式.保证安全.每个用户只能访问对应的外模式. 模式,Schema, 是数据库中全体数据的逻辑结构和特征的描述,只涉及 型Type的描述.模式的

oracle从入门到精通复习笔记

描述一个表用 desc employees过滤重复的部门 select distinct department_id from employees别名的三种方式: 1.空格 2.加as 3." "(多个单词组成的别名必须加空格,要么用下划线分开)条件匹配日期的: where to_char(date,'yyyy-mm-dd')='1997-06-07' 默认格式: where date = '7-6月-1997'like: where name like '%\_%' escape '

安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)

记得之前写过2篇关于底部菜单的实现,由于使用的是过时的TabHost类,虽然一样可以实现我们想要的效果,但作为学习,还是需要来了解下这个新引入类FragmentTabHost 之前2篇文章的链接: 安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航) 安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航) 关于Fragment类在之前的安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)也介绍过,这里就不再重复阐述了. 国际惯例,先来张效果图: 下面

计算机图形学 复习笔记

计算机图形学 复习笔记 (个人整理,仅做复习用 :D,转载注明出处:http://blog.csdn.net/hcbbt/article/details/42779341) 第一章 计算机图形学综述 研究内容 图形的概念:计算机图形学的研究对象 能在人的视觉系统中产生视觉印象的客观对象 包括自然景物.拍摄到的图片.用数学方法描述的图形等等 图形的要素 几何要素:刻画对象的轮廓.形状等 非几何要素:刻画对象的颜色.材质等 图形表示法 点阵表示 枚举出图形中所有的点,简称为图像. 参数表示 由图形的

安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)

什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再重复. 什么是Fragment? Fragment是Android3.0后新增的概念,Fragment名为碎片,不过却和Activity十分相似,具有自己的生命周期,它是用来描述一些行为或一部分用户界面在一个Activity中,我们可以合并多个Fragment在一个单独的activity中建立多个UI面板,或

安卓开发复习笔记——WebView组件

我们专业方向本是JAVA Web,这学期突然来了个手机App开发的课设,对于安卓这块,之前自学过一段时间,有些东西太久没用已经淡忘了 准备随笔记录些复习笔记,也当做温故知新吧~ 1.什么是WebView? WebView(网络视图)能加载显示网页,可以将其视为一个浏览器,它使用了WebKit渲染引擎加载显示网页. 废话不多说,直接上代码 1.需要在xml布局文件中声明WebView组件 1 <WebView 2 android:id="@+id/webview" 3 androi

oracle从入门到精通复习笔记续集之PL/SQL(轻量版)

复习内容: PL/SQL的基本语法.记录类型.流程控制.游标的使用. 异常处理机制.存储函数/存储过程.触发器. 为方便大家跟着我的笔记练习,为此提供数据库表文件给大家下载:点我下载 为了要有输出的结果,在写PL/SQL程序前都在先运行这一句:set serveroutput on结构:declare--声明变量.类型.游标begin--程序的执行部分(类似于java里的main()方法)exception--针对begin块中出现的异常,提供处理的机制--when...then...--whe

IOS开发复习笔记(3)-ARC

1.ARC 当你自己调用了release或retain语句的时候,ARC有效时编译文件会遇到错误,你可以通过-fno-objc-arc和-fobjc-arc两个编译器标志在混搭中支持ARC和非ARC的代码 如下面编译支持ARC,而文件代码不支持ARC # if !__has_feature(objc_arc) //this code do not support to ARC -(void) release{ //release your var } #endif 在ARC工程中集成非ARC的第