热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#的多线程——使用async和await来完成异步编程(AsynchronousProgrammingwithasyncandawait)

https:msdn.microsoft.comzh-cnlibrarymt674882.aspx侵删更新于:2015年6月20日欲获得最新的VisualStudio2017RC文

https://msdn.microsoft.com/zh-cn/library/mt674882.aspx

侵删

 

更新于:2015年6月20日

欲获得最新的Visual Studio 2017 RC文档,参考Visual Studio 2017 RC Documentation。

使用异步编程,你可以避免性能瓶颈和提升总体相应效率。然而,传统的异步方法代码的编写方式比较复杂,导致它很难编写,调试和维护。

Visual Studio 2012引入了一个简单的异步编程的方法,依赖.NET Framework 4.5和对应的运行时版本的支持。这样,开发者的一些复杂的工作就交给了编译器去完成,并且使你的代码保证了同步编程代码一样的逻辑结构。这样你就可以省了很多编写异步代码的时间。

本节主要介绍了何时和如何使用异步编程,包含了一些链接来展示详情和例子。

使用异步来提升相应性

异步编程对于一些存在潜在的阻塞可能的行为很重要,例如当你的应用程序需要访问网络资源的时候。连接到网络资源有时候很慢或者有延迟,这时如果这个访问时同步的,整个应用程序就要等待这个行为的结束。如果网络访问行为是异步的,这个应用程序就可以同时执行其他不依赖网络资源的工作直到这个潜在的阻塞行为结束。

下面的表格展示了一些异步编程提升响应性的场景。其中列出的.NET Framework 4.5和运行时的API包含的方法支持异步编程。

应用程序功能

支持的包含异步方法的API

网络连接

HttpClient, SyndicationClient

文件资源

StorageFile, StreamWriter, StreamReader, XmlReader

图片

MediaCapture, BitmapEncoder, BitmapDecoder

WCF

Synchronous and Asynchronous Operations

 

特别是在能够访问UI线程的应用程序中,异步编程非常有价值。因为所有与UI有关的行为都共享一个线程,如果有任何一个线程在同步进行的时候被阻塞,你的应用程序就会停止响应,尽管应用程序只是在等待,你却仍然以为它已经挂了。

当你使用异步方法, 应用程序会继续相应UI。你可以改变窗口大小或者最小化它,例如你可以关闭应用程序,如果你不想等它结束的话。

这种基于异步的编程方法让你在编写异步的时候多了一个选择——它相当于提供了一个自动转化机制。也就是说,你可以花更少的精力来完成传统异步编程的效果。

异步的方法更加容易编写

C#中的async和await关键字是异步编程的核心。通过使用这两个关键字,你可以使用 .NET Framework 或者运行时的资源来创造异步方法——几乎和你写同步方法一样。你通过使用async和await定义的方法就相当于异步方法。

下面的例子展示了一个异步方法。几乎所有的代码都看上去那么熟悉。注释段解释了一些你来用完成整个异步编程添加的特性。

在这个主题的结尾,你可以找到一个完整的WPF例子代码,你可以从Async Sample: Example from "Asynchronous Programming with Async and Await"下载。

// 在方法签名中需要注意的三个地方:  
//  - 方法必须有async修饰.   
//  - 返回类型必须是Task或者Task(看 "Return Types" 小节.)  
//    这里是Task因为返回声明返回了一个整型.  
//  - 方法名以async结尾  
async Task<int> AccessTheWebAsync()  
{   
    // 你需添加一个对System.Net.Http的引用来声明client
    HttpClient client = new HttpClient();  
  
    // GetStringAsync方法返回一个Task.这个意味着当你await这个  
    // 任务的时候,你会得到一个string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
  
    // 你可以完成一些不依赖GetStringAsync方法返回的字符串的任务  
    DoIndependentWork();  
  
    // await操作符中止了AccessTheWebAsync方法.  
    //  - AccessTheWebAsync 无法继续知道 getStringTask 完成.  
    //  - 与此同时,控制器返回AccessTheWebAsync的调用.  
    //  - 当 getStringTask 完成的时候,控制器继续进行任务.   
    //  - await操作符获得getStringTask返回的字符串.  
    string urlCOntents= await getStringTask;  
  
    // 返回声明指定了一个整型结果  
    // 任何awaiting AccessTheWebAsync的方法都会得到这个长度值 
    return urlContents.Length;  
}  

在开始调用GetStringAsync 和await这个方法完成之间,如果AccessTheWebAsync方法不包含任何其他的工作的话,你可以通过调用和await下面的声明来简化你的代码。

string urlCOntents= await client.GetStringAsync();  

下面总结了之前例子中异步方法的特性:

  • 方法签名包含async 修饰符
  • 异步方法名称按照惯例以Async结尾
  • 返回值是以下几种类型之一:
  • Task:如果你的方法包含一个操作符是TResult的返回声明
  • Task :如果你的方法没有任何返回值或者返回声明里面没有操作符
  • Void:如果你在写一个异步事件句柄

                  更多的信息可以在下面的“返回值类型和参数”获得。

  • 方法通常包含至少一个await表达式,这个await在方法标记了一个方法无法继续执行,直到需要等待的方法完成的点。在这个等待期间,方法是被暂停着的,控制器返回到方法的调用者。下一个小节会介绍这个暂停点都发生了什么。

在异步方法中,你使用这些关键字和类型去指定要做什么,编译器就会完成剩下的工作,包括继续追踪控制器在暂停的方法中返回之后要发生什么。一些例程,例如循环和异常处理,在传统的异步编程代码中很难被处理。在一个async方法中,你就像写同步代码一样地去编程,就能解决问题了。

更多的关于之前.NET Framework版本中的异步信息,参考TPL and Traditional .NET Framework Asynchronous Programming。

在一个异步方法中发生了什么

在异步编程中最重要的事情就是了解控制流如何在方法与方法之间移动,下图展示了这个过程:

技术分享

图中的数字对应以下的步骤:

  • 一个事件句柄被调用然后等待AccessTheWebAsync 这个异步方法。
  • AccessTheWebAsync 创建了一个HttpClient实例,然后调用GetStringAsync 异步方法去把一个网页以字符串形式下载。
  • GetStringAsync 方法中发生了一些事情使这个方法停止了,可能它需要等待网站响应或者其他的一些阻塞动作。为了防止阻塞资源,GetStringAsync 方法把控制还给了它的调用者AccessTheWebAsync。GetStringAsync 返回了一个Task ,TResult是一个string类型,AccessTheWebAsync 方法把任务交给了getStringTask 变量。这个task表示正在进行的GetStringAsync的调用,当这个调用完成之后会返回一个string的值。
  • 因为getStringTask 还在await中,所以AccessTheWebAsync 可以继续做其他不依赖GetStringAsync返回值的工作。这个工作可以用DoIndependentWork的调用来表示
  • DoIndependentWork 是一个完成其他的工作然后返回到它的调用者的同步方法。
  • AccessTheWebAsync 已经完成了不需要getStringTask返回值的工作。AccessTheWebAsync 接下来想要计算并且返回已经下载的string长度,但是这个方法无法计算,直到getStringTask返回一个字符串。因此,AccessTheWebAsync 使用了一个await操作符来停止整个AccessTheWebAsync方法的调用。AccessTheWebAsync返回一个Task给调用者,task意味着一个产生字符串长度的承诺。注意,如果GetStringAsync在AccessTheWebAsync等待它之前结束,控制器仍然在AccessTheWebAsync方法之中。如果getStringTask已经完成并且AccessTheWebSync不需要等待它的结果的话,暂停然后AccessTheWebAsync方法返回的异步开销就会被浪费。 在调用者内部(这个例子中的事件句柄),方法继续运行。在方法await之前,调用者可能会进行其他不需要依赖AccessTheWebAsync返回的工作,或者如果没有其他工作的话调用者可能立即await。这个事件句柄等待AccessTheWebAsync方法,AccessTheWebAsync方法等待GetStringAsync方法。
  • GetStringAsync 完成并且产生了一个string返回值。这个string返回值 并不是你想的那种返回给GetStringAsync调用者的值(要记住这个方法已经在步骤3的时候返回了一个task)。不同的是,这个string返回值被保存在task中,这个task表示getStringTask方法的结束。await操作符获得getStringTask的返回结果。赋值声明把这个结果赋值给urlContents变量。
  • 当AccessTheWebAsync得到了string结果之后,它就可以计算string长度。 AccessTheWebAsync的工作也完成了,这个等待着的句柄就可以继续。在这个专题结尾的例子中,你就可以确定这个事件句柄获得并且打印出了string返回值的长度。

如果你是异步编程的初学者,那就花一点事件考虑同步和异步编程的不同之处。一个同步方法只有等到它的工作都完成了(步骤5)才会返回,而一个异步方法在等待的时候(步骤3和6)会返回一个task。当异步方法最终完成他的工作的时候,task会被标记成完成,并且它的结果,如果有的话,会被保存在task中。

想获得更多的信息,请参阅Control Flow in Async Programs (C#)

异步方法的API

你可能会想知道去哪里找到类似于GetStringAsync 的异步方法。.NET Framework 4.5或者更高的版本都支持async和await。你可以通过观察方法名后面是否存在“Async”后缀,返回类型是Task 还是Task来判断他们是不是异步的。例如,System.IO.Stream 类就包含类似于CopyToAsync, ReadAsync 和 WriteAsync 的方法,作为CopyTo, Read 和Write的补充。

运行时也包含很多你可以在窗口应用程序中使用async和await的方法。更多信息和例子,参阅Quickstart: using the await operator for asynchronous programming, Asynchronous programming (Windows Store apps), and WhenAny: Bridging between the .NET Framework and the Windows Runtime (C#).

线程

异步方法是一个非阻塞的操作。当await的任务运行的时候,异步方法中的await表达式不会阻塞当前线程。并且这个表达式会继续执行方法中剩下的代码然后把控制还给异步方法的调用者。

async和await关键字不会生成额外的线程。异步方法不需要多线程,因为一个异步方法并不是在它自己的线程中运行的。这个方法在当前的同步上下文中运行,并且仅仅在这个方法被激活的时候才耗费时间。你可以使用Task.Run 来把对CPU性能敏感的任务设置成后台线程,但是后台线程不能等待其他任务完成再继续执行。

几乎所有情况下都建议使用基于async的异步编程方法。而且这种方式比对IO性能敏感的BackgroundWorker更好,因为代码更加简单并且你不需要去关心资源争夺的情况。和 Task.Run结合使用的话,异步编程比对CPU性能敏感的BackgroundWorker更好,因为异步编程将Task.Run分配到线程池的工作和你代码中的线程协调细节分开。

async和await

如果你使用async修饰符来定义一个方法的话,方法就具有以下两个功能:

  • 异步方法可以使用await来指定一个等待点。await操作符告诉编译器这个异步方法不能通过,直到需要等待的方法执行完毕。与此同时,控制器返回到该方法调用者。

       async方法中的await造成的停止,不会导致方法的推出,并且finally语句块不会执行。

  • 异步方法可以被调用它的方法await

一个异步方法通常包含一个或者多个await操作符,但是await操作符就算不存在也不会造成编译错误。如果一个异步方法没有使用await操作符的话,整个方法就会和同步方法一样,除了有一个async操作符。编译器会对这个方法发出一个警告。

async和await是上下文关键字,获取更多相关的信息和例子,可以参考:

  • async

  • await

返回值类型和参数

在.NET Framework中一个异步方法一般会返回一个Task 或者一个 Task类型。在异步方法中,一个await操作符应用在一个从其他异步方法返回的任务中。

如果方法包含指定了TResult类型的 return 声明,你可以定义一个Task作为返回值。

如果你的方法没有返回声明或者声明中不返回操作符,你可以使用Task作为返回值。

下面的例子展示如何声明和调用返回一个Task 或者一个 Task的例子:

// 签名中制定一个 Task  类型
async Task<int> TaskOfTResult_MethodAsync()  
{  
    int hours;  
    // . . .  
    // 返回声明中制定一个返回类型  
    return hours;  
}  
  
// 调用TResult_MethodAsync  
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();  
int intResult = await returnedTaskTResult;  
// 或者使用一个单行的声明  
int intResult = await TaskOfTResult_MethodAsync();  
  
// 签名指定一个 Task 
async Task Task_MethodAsync()  
{  
    // . . .  
    // 这个方法没有返回声明
}  
  
// 调用ethodAsync  
Task returnedTask = Task_MethodAsync();  
await returnedTask;  
//或者使用一个单行声明
 await Task_MethodAsync(); 

 

每一个返回的task都代表一个正在进行的工作。一个task包括了这个异步方法的运行状态和最终的结果或者抛出的异常。

一个异步方法也可以有一个void的返回类型。这个返回类型主要在定义事件句柄的时候用到,对于定义事件句柄,void的返回类型是必须的。异步事件句柄经常作为异步程序的起点。

一个void返回类型的异步方法不能被await并且这个方法的调用者不能捕获这个方法抛出的异常。

一个异步方法不能声明 ref 或者out 参数,但是这个方法可以调用包含这种类型参数的方法。

参考Async Return Types (C#)来获取更多的相关信息和例子。更多的关于如何铺货异步方法中的异常信息,参考try-catch。

运行时中的异步API包含了以下其中一种的返回类型,他们都类似于task:

  • IAsyncOperation, 对应 Task

  • IAsyncAction, 对应 Task

  • IAsyncActionWithProgress

  • IAsyncOperationWithProgress

想获得更多信息,参见 Quickstart: using the await operator for asynchronous programming.

命名惯例

按照惯例,你要在异步方法中添加async 修饰符。

当一个事件,基类或者接口暗示了其他的名字,例如你不可以重命名一个事件句柄,例如Button1_Click,你可以忽视这个惯例。(蛤??)

相关的主题和例子(Visual Studio)

标题 介绍 例子
Walkthrough: Accessing the Web by Using async and await (C#) 展示了如何把一个同步的WPF解决方案改成一个异步WPF解决方案。这个应用程序下载了几个页面。 Async Sample: Accessing the Web Walkthrough
How to: Extend the async Walkthrough by Using Task.WhenAll (C#) 添加了Task.WhenAll到之前的修改中。WhenAll让所有的下载同时开始。  
How to: Make Multiple Web Requests in Parallel by Using async and await (C#) 介绍了如何同时开启多个任务。


Async Sample: Make Multiple Web Requests in Parallel

Async Return Types (C#) 介绍了async方法可以返回的类型并且介绍了何时应该返回何种类型比较合适  
Control Flow in Async Programs (C#) 在一个异步程序中追溯一系列await表达式中的控制流 Async Sample: Control Flow in Async Programs
Fine-Tuning Your Async Application (C#) 展示如何在你的异步解决方案中添加以下的功能
- Cancel an Async Task or a List of Tasks (C#)
- Cancel Async Tasks after a Period of Time (C#)
- Cancel Remaining Async Tasks after One Is Complete (C#)
- Start Multiple Async Tasks and Process Them As They Complete (C#)
Async Sample: Fine Tuning Your Application
Handling Reentrancy in Async Apps (C#) 展示了如何当异步操作在执行的时候重新启动如何处理  
WhenAny: Bridging between the .NET Framework and the Windows Runtime (C#) 展示了如何在.NET Framework和运行时的IAsyncOperations中关联两个task类型,因此你可以和运行时方法搭配使用WhenAny Async Sample: Bridging between .NET and Windows Runtime (AsTask and WhenAny)
Async Cancellation: Bridging between the .NET Framework and the Windows Runtime (C#) 展示了如何在.NET Framework和运行时的IAsyncOperations中关联两个task类型,因此你可以和运行时方法搭配使用CancellationTokenSource。 Async Sample: Bridging between .NET and Windows Runtime (AsTask & Cancellation)
Using Async for File Access (C#) 列举展示了使用async和await来获取文件的好处。  
Task-based Asynchronous Pattern (TAP) 介绍了.NET Framework中的一个新的异步模式。
这种模式基于Task 和Task 类型
 
Async Videos on Channel 9 提供了一些关于异步编程的视频  

一个例子

下面是之前提到的WPF的项目中MainWindow.xaml.cs中的一段代码。你可以从Async Sample: Example from "Asynchronous Programming with Async and Await"下载这个例子。

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
// Add a using directive and a reference for System.Net.Http;  
using System.Net.Http;  
  
namespace AsyncFirstExample  
{  
    public partial class MainWindow : Window  
    {  
        // Mark the event handler with async so you can use await in it.  
        private async void StartButton_Click(object sender, RoutedEventArgs e)  
        {  
            // Call and await separately.  
            //Task getLengthTask = AccessTheWebAsync();  
            //// You can do independent work here.  
            //int cOntentLength= await getLengthTask;  
  
            int cOntentLength= await AccessTheWebAsync();  
  
            resultsTextBox.Text +=  
                String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);  
        }  
  
        // Three things to note in the signature:  
        //  - The method has an async modifier.   
        //  - The return type is Task or Task. (See "Return Types" section.)  
        //    Here, it is Task because the return statement returns an integer.  
        //  - The method name ends in "Async."  
        async Task<int> AccessTheWebAsync()  
        {   
            // You need to add a reference to System.Net.Http to declare client.  
            HttpClient client = new HttpClient();  
  
            // GetStringAsync returns a Task. That means that when you await the  
            // task you‘ll get a string (urlContents).  
            Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
  
            // You can do work here that doesn‘t rely on the string from GetStringAsync.  
            DoIndependentWork();  
  
            // The await operator suspends AccessTheWebAsync.  
            //  - AccessTheWebAsync can‘t continue until getStringTask is complete.  
            //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
            //  - Control resumes here when getStringTask is complete.   
            //  - The await operator then retrieves the string result from getStringTask.  
            string urlCOntents= await getStringTask;  
  
            // The return statement specifies an integer result.  
            // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
            return urlContents.Length;  
        }  
  
        void DoIndependentWork()  
        {  
            resultsTextBox.Text += "Working . . . . . . .\r\n";  
        }  
    }  
}  
  
// Sample Output:  
  
// Working . . . . . . .  
  
// Length of the downloaded string: 41564.  

C#的多线程——使用async和await来完成异步编程(Asynchronous Programming with async and await)


推荐阅读
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文介绍了一种解析GRE报文长度的方法,通过分析GRE报文头中的标志位来计算报文长度。具体实现步骤包括获取GRE报文头指针、提取标志位、计算报文长度等。该方法可以帮助用户准确地获取GRE报文的长度信息。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
author-avatar
mobiledu2502878565
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有