C#异步编程(async和await)

在C#中,如果需要 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统),则需要利用异步编程。 还可以使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,写法简单易用。异步编程其实也就是Task实现的多线程。以下主要介绍C# 异步编程(async和await)。

1、异步编程简介

异步编程的核心是 TaskTask<T> 对象,这两个对象对异步操作建模。 它们受关键字 asyncawait 的支持。 在大多数情况下模型十分简单:

对于 I/O 绑定代码,等待一个在 async 方法中返回 TaskTask<T> 的操作。

对于 CPU 绑定代码,等待一个使用 Task.Run 方法在后台线程启动的操作。

通过使用异步编程,可以避免性能瓶颈并增强应用程序的总体响应能力。 但是,编写异步应用程序的传统技术可能比较复杂,使它们难以编写、调试和维护。C# 5 引入了一种简便方法,即异步编程。此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 和 Windows 运行时中的异步支持。 编译器可执行开发人员曾进行的高难度工作,且应用程序保留了一个类似于同步代码的逻辑结构。 因此,只需做一小部分工作就可以获得异步编程的所有好处。

await 关键字有这奇妙的作用。 它控制执行 await 的方法的调用方,且它最终允许 UI 具有响应性或服务具有灵活性。 使用asyncawait实现的异步代码,也可以使用Task对象的Wait()等方法替代。

 

1)通过指定Url下载数据(I/O 绑定)

希望点击按钮时从指定Url下载某些数据,但不希望阻止 UI 线程。 此时可以异步编程。

例如,

private readonly HttpClient _httpClient = new HttpClient();
downloadButton.Clicked += async(o,e) =>
{
    //这一行将把控制交给UI作为请求
    //从web服务正在下载。
    // UI线程现在可以自由执行其他工作。
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

 

2)执行游戏计算(CPU绑定)

编写游戏时,按下某个按钮,将会对许多敌人造成伤害。执行伤害计算的开销可能极大,而且在 UI 线程中执行计算有可能使游戏在计算执行过程中卡住。

解决方法是启动一个后台线程,它使用 Task.Run 执行工作,并使用 await 等待其结果。 这可确保在执行工作时 UI 能流畅运行。

例如,

private DamageResult CalculateDamageDone()
{
    //省略实现代码
    //执行一个耗时的计算并返回
    //计算的结果。
}
calculateButton.Clicked += async(o,e) =>
{
    //该行将在CalculateDamageDone()时将控制权交给用户界面
    //执行它的工作。UI线程可以自由地执行其他工作。
    var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};

 

异常编程实现可以无需手动管理后台线程,而是通过非阻止性的方式来实现。

2、从网站Url获取返回的内容

使用 ASP.NET 定义 Web API 控制器方法,该方法将执行此任务并返回的内容。

例如,

private readonly HttpClient _httpClient = new HttpClient();
[HttpGet,Route("GetContent")]
public async Task<int> GetContent()
{
    //挂起GetContent()来允许调用者(web服务器)
    //接收另一个请求,而不是阻塞这个请求。
    var result = await _httpClient.GetStringAsync("https://dotnetfoundation.org");
    return result;
}

 

使用WinForm通过点击按钮使用HttpClient异步请求获取内容:

例如,

private readonly HttpClient _httpClient = new HttpClient();
private async void OnGetContentButtonClick(object sender,RoutedEventArgs e)
{
    // //捕获任务句柄,以便稍后等待后台任务。
    var getContentFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");
    // UI线程上的任何其他工作都可以在这里完成,例如启用一个进度条。
    //这是很重要的,在"await"调用之前,以便用户
    //在执行此方法之前查看进度条。
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;
    //等待操作符挂起OnGetContentButtonClick(),将控制返回给调用者。
    //这是什么允许应用程序响应,而不是阻塞UI线程。
    var result = await GetContentFoundationHtmlTask;
    DotNetCountLabel.Text = result;
    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

 

3、等待多个任务完成

可能需要并行检索多个数据部分的情况。 Task API 包含两种方法(即 Task.WhenAllTask.WhenAny),这些方法允许你编写在多个后台作业中执行非阻止等待的异步代码。

例如,

public async Task<User> GetUserAsync(int userId)
{
    // 省略实现代码:
    //给定用户Id {userId},检索对应的user对象
    //在数据库中使用{userId}作为Id的条目。
}
public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();
    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }
    return await Task.WhenAll(getUserTasks);
}

 

或者,使用 LINQ实现:

例如,

public async Task<User> GetUserAsync(int userId)
{
    // 省略实现代码:
    //给定用户Id {userId},检索对应的user对象
    //在数据库中使用{userId}作为Id的条目。
}
public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

 

注意:将LINQ 和异步代码一起使用时需要谨慎。 因为 LINQ 使用延迟的执行,因此异步调用将不会像在 foreach 循环中那样立刻发生,除非通过调用 .ToList().ToArray() 强制生成序列。

4、异步方法易于编写

C# 中的 asyncawait 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的异步方法简称为“异步方法”。

例如,

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://docs.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

 

注意:从方法签名开始。 它包含 async 修饰符。 返回类型为 Task<int>。方法名称以 Async 结尾。 在方法的主体中,GetStringAsync 返回 Task<string>。  await 运算符。 它会暂停 GetUrlContentLengthAsync,当 getStringTask 完成时,控件将在此处继续。

如果 GetUrlContentLengthAsync 在调用 GetStringAsync 和等待其完成期间不能进行任何工作,则你可以通过在下面的单个语句中调用和等待来简化代码。

例如,

string contents = await client.GetStringAsync("https://docs.microsoft.com/dotnet");

1)方法签名包含 async 修饰符。

2)按照约定,异步方法的名称以“Async”后缀结尾。

3)返回类型为下列类型之一:

  • 如果方法有操作数为 TResult 类型的返回语句,则为 Task<TResult>
  • 如果方法没有返回语句或具有没有操作数的返回语句,则为 Task
  • void:如果要编写异步事件处理程序。
  • 包含 GetAwaiter 方法的其他任何类型(自 C# 7.0 起)。

4)方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方。

5、API异步方法以及async和await关键字

.NET Framework 4.5 或更高版本以及 .NET Core 包含许多可与 asyncawait 结合使用的成员。 可以通过追加到成员名称的“Async”后缀和 TaskTask<TResult> 的返回类型,识别这些成员。 例如,System.IO.Stream 类包含 CopyToAsyncReadAsyncWriteAsync 等方法,以及同步方法 CopyToReadWrite

异步方法旨在成为非阻止操作。 异步方法中的 await 表达式在等待的任务正在运行时不会阻止当前线程。 相反,表达式在继续时注册方法的其余部分并将控件返回到异步方法的调用方。asyncawait 关键字不会创建其他线程。 因为异步方法不会在其自身线程上运行,因此它不需要多线程。

只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。 可以使用 Task.Run 将占用大量 CPU 的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。

async 和 await的作用:

1)标记的异步方法可以使用 await 来指定暂停点。 await 运算符通知编译器异步方法:在等待的异步过程完成后才能继续通过该点。 同时,控制返回至异步方法的调用方。 异步方法在 await 表达式执行时暂停并不构成方法退出,只会导致 finally 代码块不运行。

2)标记的异步方法本身可以通过调用它的方法等待。

异步方法通常包含 await 运算符的一个或多个实例,但缺少 await 表达式也不会导致生成编译器错误。 如果异步方法未使用 await 运算符标记暂停点,则该方法会作为同步方法执行,即使有 async 修饰符,也不例外。 编译器将为此类方法发布一个警告。

6、使用 IAsyncEnumerable<T> 的异步流

从 C# 8.0 开始,异步方法可能返回异步流,由 IAsyncEnumerable<T> 表示 。 异步流提供了一种方法,来枚举在具有重复异步调用的块中生成元素时从流中读取的项。

例如,

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
    string data =
        @"https://www.baidu.com";

    using var readStream = new StringReader(data);

    string line = await readStream.ReadLineAsync();
    while (line != null)
    {
        foreach (string word in line.Split(' ',StringSplitOptions.RemoveEmptyEntries))
        {
            yield return word;
        }

        line = await readStream.ReadLineAsync();
    }
}

 

原文地址:https://blog.csdn.net/lwf3115841/article/details/130876976

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。 CSV文件标准 在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。 文件示例 一
简介 本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。 首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和
这篇文章主要简单记录一下C#项目的dll文件管理方法,以便后期使用。 设置dll路径 参考C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁中间的 方法一:配置App.config文件的privatePath : &lt;runtime&gt; &lt;assemblyBinding xml
在C#中的使用JSON序列化及反序列化时,推荐使用Json.NET——NET的流行高性能JSON框架,当然也可以使用.NET自带的 System.Text.Json(.NET5)、DataContractJsonSerializer、JavaScriptSerializer(不推荐)。
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。&#xA;EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。
通用翻译API的HTTPS 地址为https://fanyi-api.baidu.com/api/trans/vip/translate,使用方法参考通用翻译API接入文档 。&#xA;请求方式可使用 GET 或 POST 方式(Content-Type 请指定为:application/x-www-for
词云”由美国西北大学新闻学副教授、新媒体专业主任里奇·戈登(Rich Gordon)于2006年最先使用,是通过形成“关键词云层”或“关键词渲染”,对文本中出现频率较高的“关键词”的视觉上的突出。词云图过滤掉大量的文本信息,使浏览者只要一眼扫过文本就可以领略文本的主旨。&#xA;网上大部分文章介绍的是使用P
微软在.NET中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。&#xA;为操作方便,本文对SerialPort类做了一些封装,暂时取名为**SerialPortClient**。
简介 管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名
目录自定义日志类NLog版本的日志类Serilog版本的日志类 上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。 自定义日志类 代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点: 线程安全,日志异步写入文件不影响业务逻辑 支持过期文件自动清理,也可自
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法](https://blog.csdn.net/weixin_42288432/article/details/120059296),将里面中的第一种方法做了封装成**AutoStart**类,使
简介 FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。 FTP
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。&#xA;在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
# 简介 主流的识别库主要有ZXing.NET和ZBar,OpenCV 4.0后加入了QR码检测和解码功能。本文使用的是ZBar,同等条件下ZBar识别率更高,图片和部分代码参考[在C#中使用ZBar识别条形码](https://www.cnblogs.com/w2206/p/7755656.htm
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用。 扩展类DescriptionExtension代码如下: using System; using System.ComponentModel; using System.Reflection; /// &lt;
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:保存配置到json文件、从文件或实例加载配置类的属性值、数据绑定到界面控件。&#xA;一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。&#xA;最常用的泛型约束为
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。&#xA;protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https:/
工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了。这时候就可以使用SimpleTCP了,当然仅限于C#语言。&#xA;SimpleTCP是一个简单且非常有用的 .NET 库,用于处理启动和使用 TCP 套接字(客户端和服务器)的重复性任务