C# 同步异步大白话

news2024/12/24 9:22:52

同步异步大白话

背景

任务异步编程模型(TAP)提供了对异步代码的抽象。您可以像往常一样,将代码编写为一系列语句。您可以阅读该代码,就好像每条语句都在下一条语句开始之前完成一样。编译器执行许多转换,因为其中一些语句可能开始工作并返回表示正在进行的工作的Task

这就是该语法的目标:启用读起来像一系列语句的代码,但根据外部资源分配和任务完成时,代码的执行顺序要复杂得多。这类似于人们如何为包含异步任务的流程提供指令。在本文中,您将使用一个制作早餐的指令示例来了解async和wait关键字如何使您更容易推理包含一系列异步指令的代码。你可以写下类似以下列表的说明来解释如何做早餐:

  1. 倒一杯咖啡。
  2. 把锅烧热
  3. 然后煎两个鸡蛋。
  4. 煎三片培根。
  5. 烤两片面包。
  6. 在烤面包上加黄油和果酱。
  7. 倒一杯橙汁。

如果你有烹饪经验,你会异步执行这些指令。你应该先把锅里的鸡蛋加热,然后再开始培根。你应该把面包放进烤面包机,然后开始煮鸡蛋。在这个过程的每一步,你都会开始一项任务,然后把注意力转移到准备好引起你注意的任务上。

烹饪早餐是异步工作的一个很好的例子。一个人(或线程)可以处理所有这些任务。继续早餐的类比,一个人可以在第一个任务完成之前开始下一个任务,异步地做早餐。不管有没有人在看,烹饪都在进行。一旦你开始加热锅里的鸡蛋,你就可以开始煎培根了。一旦培根开始烤,你就可以把面包放进烤面包机了。

对于并行算法,您需要多个厨师(或线程)。一个做鸡蛋,一个做培根,等等。每一个都只专注于一项任务。每个厨师(或线程)都会被同步阻塞,等待培根准备翻转或烤面包爆裂。

让我们从更新这段代码开始,这样线程在任务运行时就不会阻塞。await关键字提供了一种非阻塞的方式来启动任务,然后在任务完成后继续执行。

在这里插入图片描述

在这里插入图片描述

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

不要阻塞,等待

前面的代码演示了一种糟糕的做法:构造同步代码来执行异步操作。正如所写的,这段代码会阻止执行它的线程执行任何其他工作。当任何任务正在进行时,它都不会被中断。就好像你把面包放进去后盯着烤面包机看一样。你会忽略任何和你说话的人,直到烤面包爆裂。

让我们从更新这段代码开始,这样线程在任务运行时就不会阻塞。await关键字提供了一种非阻塞的方式来启动任务,然后在任务完成后继续执行。生成早餐代码的一个简单异步版本如下所示:

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    Egg eggs = await FryEggsAsync(2);
    Console.WriteLine("eggs are ready");

    Bacon bacon = await FryBaconAsync(3);
    Console.WriteLine("bacon is ready");

    Toast toast = await ToastBreadAsync(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

当鸡蛋或培根烹饪时,此代码不会被阻止。不过,此代码不会启动任何其他任务。你仍然会把烤面包放在烤面包机里,盯着它看,直到它爆裂。但至少,你会回应任何想引起你注意的人。在一家有多份订单的餐厅里,厨师可以在第一份正在烹饪的时候开始另一份早餐。

同时启动任务 Start tasks concurrently

在许多情况下,您希望立即启动几个独立的任务。然后,随着每项任务的完成,您可以继续其他已准备好的工作。在早餐的比喻中,这就是你如何更快地完成早餐的方法。你还可以在几乎同一时间完成所有事情。你会得到一顿热早餐。

System.Threading.Tasks.Task和相关类型是可用于推理正在进行的任务的类。这使您能够编写与创建早餐更相似的代码。你应该同时开始煮鸡蛋、培根和烤面包。由于每一项都需要行动,你会把注意力转向那项任务,做好下一项行动,然后等待其他需要你注意的事情。

您启动一个任务并抓住代表该工作的task对象。您将等待每个任务,然后再处理其结果。

让我们对早餐代码进行这些更改。第一步是在操作开始时存储任务,而不是等待它们:

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");

Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");

在这里插入图片描述
异步准备的早餐的最终版本大约需要6分钟,因为有些任务是并发运行的,并且代码同时监视多个任务,并且只在需要时才采取行动。

最后的代码是异步的。它更准确地反映了一个人如何做早餐。将前面的代码与本文中的第一个代码示例进行比较。通过阅读代码,核心操作仍然清晰可见。您可以像阅读本文开头的早餐制作说明一样阅读此代码。async和await的语言功能为每个人提供了遵循这些书面指令的翻译:尽可能启动任务,不要阻止等待任务完成。

在这里插入图片描述

任务组成 Composition with tasks

除了烤面包,你早餐的所有东西都同时准备好了。
烤面包是由异步操作(烤面包)和同步操作(添加黄油和果酱)组成的。更新此代码说明了一个重要概念:

重要的
异步操作和同步工作的组合是异步操作。换句话说,如果操作的任何部分是异步的,那么整个操作就是异步的。

前面的代码向您展示了可以使用TaskTask<TResult>对象来保存正在运行的任务。您等待每个任务,然后再使用其结果。下一步是创建表示其他工作组合的方法。在供应早餐之前,您需要等待代表在添加黄油和果酱之前烤面包的任务。您可以使用以下代码来表示该工作:

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

前面的更改说明了使用异步代码的一项重要技术。通过将操作分离到返回任务的新方法中,可以组合任务。您可以选择何时等待该任务。您可以同时启动其他任务。

高效地等待任务

前面代码末尾的一系列等待语句可以通过使用Task类的方法进行改进。

  • 其中一个API是WhenAll,它返回一个在其参数列表中的所有任务都已完成时完成的Task,如以下代码所示:
await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");
  • 另一个选项是使用WhenAny,它返回一个Task<Task>,当它的任何参数完成时,它就完成了。

您可以等待返回的任务,知道它已经完成。下面的代码显示了如何使用WhenAny来等待第一个任务完成,然后处理其结果。处理完已完成任务的结果后,将从传递给WhenAny的任务列表中删除该已完成任务。

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == eggsTask)
    {
        Console.WriteLine("Eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("Bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("Toast is ready");
    }
    await finishedTask;
    breakfastTasks.Remove(finishedTask);
}

现在,在等待任何尚未完成的已启动任务时,处理早餐的线程不会被阻塞。对于某些应用程序,只需要进行此更改。GUI应用程序仍然只通过这个更改来响应用户。但是,对于这种情况,您需要更多。您不希望每个组件任务都按顺序执行。在等待前一个任务完成之前,最好先启动每个组件任务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194188.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最详细的LightGBM参数介绍与深入分析

前言 我使用LightGBM有一段时间了&#xff0c;它一直是我处理大多数表格数据问题的首选算法。它有很多强大的功能&#xff0c;如果你还没有看过的话&#xff0c;我建议你去了解一下。 但我一直对了解哪些参数对性能影响最大&#xff0c;以及如何调整LightGBM参数以发挥最大作用…

使用WinDbg分析CPU100%的问题

在我们软件运行的时候&#xff0c;偶尔会出现CPU占比100%的问题&#xff0c;而且极其不容易排查&#xff0c;概率极低&#xff0c;我硬是操作了一个下午&#xff0c;出现了一次&#xff0c;然后找到了dmp文件&#xff0c;也没有任何的规律&#xff0c;那么就可以借助windbg进行…

二维码解码器怎么用?快速分解二维码图片的方法

现在很多人会将链接网址生成二维码之后来使用&#xff0c;这种方式能够让别人更快的获取链接的内容&#xff0c;而且扫码访问内容的方式也更适合大家的使用习惯。那么如果想要获取二维码中的链接时&#xff0c;一般会使用二维码解码器来处理&#xff0c;那么具体该怎么使用呢&a…

nodejs express multer 保存文件名为中文时乱码,问题解决 originalname

nodejs express multer 保存文件名为中文时乱码&#xff0c;问题解决 originalname 一、问题描述 用 express 写了个后台&#xff0c;在接收文件并保存的时候 multer 接收到的文件名为乱码。 二、解决 找了下解决方法&#xff0c;在 github 的 multer issue 中找到了答案 参…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

归并排序之确定递归层数

题目 给定一维int型数组a[0,1,…,n-1], 使用归并排序方法, 对其进行从小到大排序, 请输出递归过程中自顶自下第三层的排序结果, 其中最顶层为第一层, 即最终的排序结果层. 归并排序划分请按a[0,mid(0n-1)/2], a[(0n-1)/21, n-1]进行划分子问题. Input 输入第1行有一个int型正…

AI由许多不同的技术组成,其中一些最核心的技术如下

AI由许多不同的技术组成&#xff0c;其中一些最核心的技术包括&#xff1a; 机器学习&#xff1a;这是一种让计算机从数据中学习的技术&#xff0c;它可以根据已有的数据预测未来的趋势和行为。机器学习包括监督学习、无监督学习和强化学习等多种类型。深度学习&#xff1a;这…

Flink SQL -- 命令行的使用

1、启动Flink SQL 首先启动Flink的集群&#xff0c;选择独立集群模式或者是session的模式。此处选择是时session的模式&#xff1a;yarn-session.sh -d 在启动Flink SQL的client&#xff1a; sql-client.sh 2、kafka SQL 连接器 在使用kafka作为数据源的时候需要上传jar包到…

Flutter笔记:光影动画按钮、滚动图标卡片组等

Flutter笔记 scale_design更新&#xff1a;光影动画按钮、滚动图标卡片组 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263…

又一重要合作,创邻科技华为云联营产品正式发布

近日&#xff0c;创邻科技旗下的“Galaxybase高性能图平台”正式入驻华为云云商店联营商品&#xff0c;创邻科技成为华为云在数据库与缓存领域的联营联运合作伙伴。通过联营联运模式&#xff0c;双方合作能够深入产品、生态、解决方案等多个领域&#xff0c;助力各行业用户数字…

农业大棚智能化改造升级与远程视频监管方案,助力智慧农业建设发展

一、需求分析 随着现代化技术的发展&#xff0c;农业大棚的智慧化也成为当前备受关注的智慧农业发展手段。利用先进的信息化手段来对农业大棚进行管理&#xff0c;采集和掌握作物的生长状况、作业监督、生态环境等信息数据&#xff0c;实现精准操作、精细管理&#xff0c;远程…

SMART PLC模拟量上下限报警功能块(梯形图代码)

博途PLC模拟量偏差报警功能块请参考下面的文章链接: 模拟量偏差报警功能块(SCL代码)_RXXW_Dor的博客-CSDN博客文章浏览阅读594次。工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘述,常用链接如下:PLC模拟量采集算法数学基础(线性传感器)_plc傳感器數…

Leetcode—637.二叉树的层平均值【简单】

2023每日刷题&#xff08;二十五&#xff09; Leetcode—637.二叉树的层平均值 BFS实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array mu…

归并分治 归并排序的应用 + 图解 + 笔记

归并分治 前置知识&#xff1a;讲解021-归并排序 归并排序 图解 递归 非递归 笔记-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/134338789?spm1001.2014.3001.5501原理&#xff1a; (1&#xff09;思考一个问题在大范围上的答案&#xff0c;是否等于&…

c语言break和continue语句用法

作用 break语句&#xff1a;可用于循环结构和开关结构(switch)中&#xff0c;在开关语句中的作用是执行完当前case后立即跳出switch结构。在循环语句中的作用是终止当前层的循环。continue语句&#xff1a;作用是跳过循环体中剩余的语句而强行执行下一次循环。 区别 continue…

golang 库之「依赖注入」

文章目录 1. 写在最前面2. 依赖注入2.1 使用场景2.2 框架对比 3. fx 框架使用场景示例3.1 示例3.2 golang 原生的库3.3 fx 库3.4 对比3.4.1 如上两种实现方式对比3.4.2 关于过度设计3.4.3 感悟 4. 碎碎念5. 参考资料 1. 写在最前面 同事在技术分享的时候用了 golang 的 fx 框架…

chatglm3-6b记录问答对

# 打开文件,第二个参数是打开文件的模式&#xff0c;a代表追加&#xff0c;也就是说&#xff0c;打开这个文件之后直接定位到文件的末尾 file open(chatlog.txt, "a") # 写入数据 file.write(ask:prompt_text\n) file.write(response:response\n) # 关闭文件 fil…

基于SSM的水果网上商城设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

Ubuntu 安装常见问题

1. 安装oh my zsh 搜狗输入法不能用 vim /etc/environmentexport XIM_PROGRAMfcitx export XIMfcitx export GTK_IM_MODULEfcitx export QT_IM_MODULEfcitx export XMODIFIERS“imfcitx” export LANG“zh_CN.UTF-8”配置完后重启&#xff0c;稍等一会&#xff0c;右上角会有个…

35 字段类型不匹配 影响 使用索引?

前言 这是一个经常能够看到的问题, 又或者 经常在面试中碰到 如果 索引字段类型 不匹配, 然后 不会使用索引 这里 我们来看一下 具体的情况 测试表结构如下 CREATE TABLE tz_test (id int(11) unsigned NOT NULL AUTO_INCREMENT,field1 varchar(128) DEFAULT NULL,PRIMA…