当前位置: 首页 > article >正文

windows C#-异步编程场景(一)

如果需要 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统),则需要利用异步编程。 还可以使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,这是一个不错的方案。

C# 拥有语言级别的异步编程模型,让你能轻松编写异步代码,而无需应付回调或受限于支持异步的库。 它遵循基于任务的异步模式 (TAP)。

异步模型概述

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

对于 I/O 绑定代码,等待一个在 async 方法中返回 Task 或 Task<T> 的操作。
对于 CPU 绑定代码,等待一个使用 Task.Run 方法在后台线程启动的操作。
await 关键字有这奇妙的作用。 它控制执行 await 的方法的调用方,且它最终允许 UI 具有响应性或服务具有灵活性。 虽然有方法可处理 async 和 await 以外的异步代码,但本文重点介绍语言级构造。

在以下一些示例中,System.Net.Http.HttpClient 类用于从 Web 服务下载某些数据。 这些示例中使用的 s_httpClient 对象是 Program 类的静态字段请检查完整示例:

private static readonly HttpClient s_httpClient = new();

I/O 绑定示例:从 Web 服务下载数据

你可能需要在按下按钮时从 Web 服务下载某些数据,但不希望阻止 UI 线程。 可执行如下操作来实现:

s_downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await s_httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

代码表示目的(异步下载数据),而不会在与 Task 对象的交互中停滞。

CPU 绑定示例:为游戏执行计算

假设你正在编写一个移动游戏,在该游戏中,按下某个按钮将会对屏幕中的许多敌人造成伤害。 执行伤害计算的开销可能极大,而且在 UI 线程中执行计算有可能使游戏在计算执行过程中暂停!

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

static DamageResult CalculateDamageDone()
{
    return new DamageResult()
    {
        // Code omitted:
        //
        // Does an expensive calculation and returns
        // the result of that calculation.
    };
}

s_calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work. The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

此代码清楚地表达了按钮的单击事件的目的,它无需手动管理后台线程,而是通过非阻止性的方式来实现。

内部原理

在 C# 方面,编译器将代码转换为状态机,它将跟踪类似以下内容:到达 await 时暂停执行以及后台作业完成时继续执行。

从理论上讲,这是异步的承诺模型的实现。

需了解的要点

  • 异步代码可用于 I/O 绑定和 CPU 绑定代码,但在每个方案中有所不同。
  • 异步代码使用 Task<T> 和 Task,它们是对后台所完成的工作进行建模的结构。
  • async 关键字将方法转换为异步方法,这使你能在其正文中使用 await 关键字。
  • 应用 await 关键字后,它将挂起调用方法,并将控制权返还给调用方,直到等待的任务完成。
  • 仅允许在异步方法中使用 await。

识别 CPU 绑定和 I/O 绑定工作

本文的前两个示例演示如何将 async 和 await 用于 I/O 绑定和 CPU 绑定工作。 确定所需执行的操作是 I/O 绑定或 CPU 绑定是关键,因为这会极大影响代码性能,并可能导致某些构造的误用。

以下是编写代码前应考虑的两个问题:

  • 你的代码是否会“等待”某些内容,例如数据库中的数据?如果答案为“是”,则你的工作是 I/O 绑定。
  • 你的代码是否要执行开销巨大的计算?如果答案为“是”,则你的工作是 CPU 绑定。

如果你的工作为 I/O 绑定,请使用 async 和 await(而不使用 Task.Run)。 不应使用任务并行库。

如果你的工作属于 CPU 绑定,并且你重视响应能力,请使用 async 和 await,但在另一个线程上使用 Task.Run 生成工作。 如果该工作同时适用于并发和并行,还应考虑使用任务并行库。

此外,应始终对代码的执行进行测量。 例如,你可能会遇到这样的情况:多线程处理时,上下文切换的开销高于 CPU 绑定工作的开销。 每种选择都有折衷,应根据自身情况选择正确的折衷方案。

从网络提取数据

此代码片段从给定的 URL 下载 HTML,并计算 HTML 中字符串“.NET”的出现次数。 它使用 ASP.NET 定义 Web API 控制器方法,该方法将执行此任务并返回数字。如果打算在生产代码中进行 HTML 分析,则不要使用正则表达式。 改为使用分析库。

[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCount(string URL)
{
    // Suspends GetDotNetCount() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await s_httpClient.GetStringAsync(URL);
    return Regex.Matches(html, @"\.NET").Count;
}

以下是为通用 Windows 应用编写的相同方案,当按下按钮时,它将执行相同的任务:

private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
    // This is what allows the app to be responsive and not block the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, @"\.NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

http://www.kler.cn/a/402075.html

相关文章:

  • AntFlow 0.11.0版发布,增加springboot starter模块,一款设计上借鉴钉钉工作流的免费企业级审批流平台
  • [数组二分查找] 0209. 长度最小的子数组
  • Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析20241119
  • 《生成式 AI》课程 第3講 CODE TASK执行文章摘要的机器人
  • 【网络】Socket编程TCP/UDP序列化和反序列化理解应用层(C++实现)Json::Value
  • qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能
  • 【前端知识】Javascript前端框架Vue入门
  • 代码随想录算法训练营第五十一天|Day51 图论
  • 基于机器学习电信号EMG训练分类模型控制仿生手控制系统(Matlab-Simulink实现)
  • 使用Axios函数库进行网络请求的使用指南
  • 在spring boot工程中使用Filter时,@WebFilter 注解不生效的问题分析和解决方案
  • OSPF的练习
  • Github 2024-11-16Rust开源项目日报 Top10
  • golang操作mysql基础驱动github.com/go-sql-driver/mysql使用
  • A - 123233(atCoder-380刷题笔记)
  • WebView2的踩坑记
  • Pr:音频过渡
  • 深度学习的多主机多GPU协同训练
  • 【C++学习(37)】并发性模式:如生产者-消费者、读写锁等。 架构模式:如MVC、MVVM等。属于23 种设计模式吗? RAII 的关系?
  • 传奇996_23——杀怪掉落,自动捡取,捡取动画
  • Ribbon 与 Feign:微服务调用中的差异探究
  • Linux网络——套接字编程
  • 学习记录:js算法(九十五):被围绕的区域
  • 2019年下半年试题二:论软件系统架构评估及其应用
  • Node.js | Yarn下载安装与环境配置
  • 【JAVA】正则表达式中的正向肯定预查