C# 异步编程

异步编程是一种思路
异步相当于对线程池的封装
await相当于让另一个线程来干这个事	

  • 常见概念
    • 已经有多线程了,为何还要异步
      • 多线程与异步是不同的概念
      • 多线程与异步的适用场景不同
        • *多线程*
        • *异步*
    • 什么是异步任务(`Task`)
      • 包含了异步任务的各种状态的一个引用类型
      • 对于异步任务的抽象
      • 任务的结果
    • 异步方法(`async Task`)
          • *`async Task`*
    • 重要思想:不阻塞!
        • *常见阻塞情形*
    • 同步上下文
        • *`ConfigureAwait(false)`*
    • 一发即忘(Fire-and-forget)
  • 简单任务
    • 如何创建异步任务?
        • `Task.Run()`
        • `Task.Factory.StartNew()`
        • `new Task` + `Task.Start()`
        • 例子
    • 如何同时开启多个异步任务?
    • 任务如何取消?
    • 任务超时如何实现?
    • 在异步任务中汇报进度?
    • 如何在同步方法中调用异步方法?
  • 常见的误区
    • 异步一定是多线程?
    • 异步方法一定要写成async Task?
    • await一定会切换同步上下文?
    • 异步可以全面取代多线程?
    • Task.Result 一定会阻塞当前线程?
    • 开启的异步任务一定不会阻塞当前线程?
  • 同步机制
    • 传统方法
    • 轻量型
    • 并发集合
    • 第三方库



常见概念

已经有多线程了,为何还要异步

多线程与异步是不同的概念

异步并不意味着多线程,单线程同样可以异步
异步默认借助线程池
多线程经常阻塞,而异步要求不阻塞

多线程与异步的适用场景不同

多线程
适合CPU密集型操作
适合长期运行的任务
线程的创建与销毁开销较大
提供更底层的控制,操作线程、锁、信号量等
线程不易于传参及返回
线程的代码书写较为繁琐
异步
适合IO密集型操作
适合短暂的小任务
避免线程阻塞,提高系统响应能力

什么是异步任务(Task)

包含了异步任务的各种状态的一个引用类型

正在运行、完成、结果、报错等

public class TODO {
    public static event Func<object, string> foo;

    static void Main() {

        Task<string> task = new Task<string>((n) => {
            Thread.Sleep(1500);
            for (int i = 0; i < (int)n; i++) {
                Console.WriteLine("DONE {0}", i);
            }
            return "ok";
        }, 3);

        Console.WriteLine(task.Status);
        task.Start();
        Console.WriteLine(task.Status);
        Thread.Sleep(1000);
        Console.WriteLine(task.Status);
        Thread.Sleep(2000);
        Console.WriteLine(task.Status);
        Console.WriteLine(task.Result);
    }
}

输出结果

Created
WaitingToRun
Running
DONE 0
DONE 1
DONE 2
RanToCompletion
ok
另有ValueTask值类型版本

对于异步任务的抽象

开启异步任务后,当前线程并不会阻塞,而是可以去做其他事情
异步任务(默认)会借助线程池在其他线程上运行
获取结果后回到之前的状态

任务的结果

返回值为Task的方法表示异步任务没有返回值
返回值为Task<T>则表示有类型为T的返回值

异步方法(async Task)

将方法标记async 后,可以在方法中使用await关键字

await关键字会等待异步任务的结束,并获得结果

async + await 会将方法包装成状态机,await类似于检查点

MoveNext方法会被底层调用,从而切换状态
async Task
返回值依旧是Task类型,但是在其中可以使用await关键字
在其中写返回值可以直接写Task<T> 中的T类型,不用包装成Task<T>

async void

同样是状态机,但缺少记录状态的Task对象
无法聚合异常(Aggregate Exception),需要谨慎处理异常
几乎只用于对于事件的注册

*异步编程具有传染性(Contagious)
*
一处async,处处async
几乎所有自带方法都提供了异步的版本


重要思想:不阻塞!

await会暂时释放当前线程,使得该线程可以执行其他工作,而不必阻塞线程直到异步操作完成

不要在异步方法里用任何方式阻塞当前线程

常见阻塞情形

Task.Wait() & Task.Result

如果任务没有完成,则会阻塞当前线程,容易导致死锁

Task.GetAwaiter().GetResult(),不会将Exception 包装为AggregateException

Task.Delay() vs. Thread.Sleep()

后者会阻塞当前的线程,这与异步编程的理念不符
前者是一个异步任务,会立刻释放当前的线程

IO等操作的同步方法

其他繁重且耗时的任务


同步上下文

一种管理和协调线程的机制,允许开发者将代码的执行切换到特定的线程

WinFormsWPF拥有同步上下文(UI 线程),而控制台程序默认没有

ConfigureAwait(false)
配置任务通过await方法结束后是否会到原来的线程,默认为true
一般只有UI线程会采用这种策略

TaskScheduler

控制Task的调度方式和运行线程

线程池线程Default
当前线程CurrentThread
单线程。上下文STAThread
长时间运行线程LongRunning

优先级、上下文、执行状态等


一发即忘(Fire-and-forget)

调用一一个异步方法,但是并不使用await或阻塞的方式去等待它的结束
无法观察任务的状态(是否完成、是否报错等)



简单任务

如何创建异步任务?

Task.Run()
Task.Factory.StartNew()
提供更多功能,比如TaskCreationOptions.L ongRunning
Task.Run相当于简化版
new Task + Task.Start()
很少有创建一个Task却没有让他立刻开始的
例子
public class TODO {

    static async Task Main() {
        
        Console.WriteLine(12);
        Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString());
        var task = await Task.Run(heavyJob);
        //Console.WriteLine(task);
        Console.WriteLine(123);
    }
    public static int heavyJob() {
        Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString());
        Thread.Sleep(10);
        return 1;
    }


}


如何同时开启多个异步任务?

public class TODO {
    static async Task Main() {
        var inputs = Enumerable.Range(10, 10).ToArray();
        var tasks = new List<Task<int>>();
        Console.WriteLine(Environment.CurrentManagedThreadId);
        foreach (var input in inputs) {
            tasks.Add(foo(input));
        }
        await Task.WhenAll(tasks);
        var outputs = tasks.Select(x => x.Result).ToArray();
        foreach (var output in outputs) {
            Console.WriteLine(output);
        }
    }
    public static async Task<int> foo(int input) {
        await Task.Delay(5000);
        return input * 2;
    }
}

任务如何取消?

CancellationTokenSource + CancellationToken

public class TODO {
    static async Task Main() {
        var cts = new CancellationTokenSource();
        try {
            var task = Task.Delay(100000, cts.Token);
            Thread.Sleep(2000);
            cts.Cancel();//抛出异常
            await task;
        } catch (TaskCanceledException) {
            Console.WriteLine("ss");
        } finally {
            cts.Cancel();
        }
    }
}

OperationCanceledException & TaskCanceledException

推荐异步方法都带上CancellationToken这一传参

你自己写了异步方法却不支持传入这个————我可以不用,但不能没有

任务超时如何实现?


在异步任务中汇报进度?


如何在同步方法中调用异步方法?



常见的误区

异步一定是多线程?

异步编程不必需要多线程来实现

比如可以在单个线程上使用异步I/O 或事件驱动的编程模型(EAP)

单线程异步:自己定好计时器,到时间之前先去做别的事情
多线程异步:将任务交给不同的线程,并由自己来进行指挥调度


异步方法一定要写成async Task?

async关键字只是用来配合await 使用,从而将方法包装为状态机

本质上仍然是Task,只不过提供了语法糖,并且函数体中可以直接return Task的泛型类型

接口中无法声明async Task


await一定会切换同步上下文?

在使用await关键字调用并等待一个异步任务时,异步方法不一定会立刻来到新的线程上

如果await了一个已经完成的任务(包括Task.Delay(0)),会直接获得结果


异步可以全面取代多线程?

异步编程与多线程有一定关系,但两者并不是可以完全互相替代


Task.Result 一定会阻塞当前线程?

如果任务已经完成,那么Task.Result 可以直接得到结果


开启的异步任务一定不会阻塞当前线程?

await关键字不一定会立刻释放当前线程,所以如果调用的异步方法中存在阻塞(如Thread.Sleep(O))
那么依旧会阻塞当前上下文对应的线程



同步机制

传统方法

Monitor(lock)
Mutex
Semaphore
EventWaitHandle

轻量型

所有只有SemaphoreSlim不阻塞
ManualResetEventSlim

并发集合

第三方库

AsyncManulResetEvent

Miccrosoft.VisualStudio.Threading

AsyncLock

Nito.AsyncEx

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[激光原理与应用-90]:光功率计基本原理

目录 一、光功率计原理 二、光功率计硬件电路 三、光功率计探头 四、接口信号 一、光功率计原理 光功率计是用来测量光功率的仪器&#xff0c;其原理基于光电效应和电信号的检测与处理。 下面是光功率计的基本原理&#xff1a; 光电效应&#xff1a; 光功率计使用光敏元件…

链表的分割

题目 现给定一链表的头指针 phead 以及值 x&#xff0c;需编写一段代码将所有小于 x 节点的排在其余节点之前&#xff0c;且不能改变原来的数据顺序&#xff0c;最后返回重新排列后的链表的头指针。 算法思想 将小于x的尾插在第一个链表 将大于等于x的尾插在第二个链表 最后…

1142 - SELECT command denied to user ···

MySql子账户操作数据库权限不够&#xff0c;提示错误 1142 - SELECT command denied to user database 1142 - ALTER command denied to user database 以下命令可以解决 GRANT SELEC your_database_name TO mysql_account%;

centos7上搭建mongodb数据库

1.添加MongoDB的YUM仓库&#xff1a; 打开终端&#xff0c;执行以下命令来添加MongoDB的YUM仓库&#xff1a; sudo vi /etc/yum.repos.d/mongodb-org-4.4.repo 在打开的文件中&#xff0c;输入以下内容&#xff1a; [mongodb-org-4.4] nameMongoDB Repository baseurlh…

【Mysql】用frm和ibd文件恢复mysql表数据

问题 总是遇到mysql服务意外断开之后导致mysql服务无法正常运行的情况&#xff0c;使用Navicat工具查看能够看到里面的库和表&#xff0c;但是无法获取数据记录&#xff0c;提示数据表不存在。 这里记录一下用frm文件和ibd文件手动恢复数据表的过程。 思路 1、frm文件&…

第一个Spring Boot程序

目录 一、Spring Boot介绍 二、创建Spring Boot项目 1、插件安装&#xff08;专业版不需要&#xff09; 2、创建SpringBoot项目 &#xff08;1&#xff09;这里如果插件下载失败&#xff0c;解决方案&#xff1a; &#xff08;2&#xff09;项目启动失败&#xff0c;解决…

Docker镜像与容器操作

一、Docker 镜像操作 1.1 搜索镜像 格式&#xff1a;docker search 关键字 docker search nginx 1.2 获取镜像nginx 格式&#xff1a;docker pull 仓库名称[:标签] 如果下载镜像时不指定标签&#xff0c;则默认会下载仓库中最新版本的镜像&#xff0c;即选择标签为 latest…

SwiftUI 5.0(iOS 17.0)触摸反馈“震荡波”与触发器模式趣谈

概览 要想创作出一款精彩绝伦的 App&#xff0c;绚丽的界面和灵动的动画并不是唯一吸引用户的要素。有时我们还希望让用户真切的感受到操作引发的触觉反馈&#xff0c;直击使用者的灵魂。 所幸的是新版 SwiftUI 原生提供了实现触觉震动反馈的机制。在介绍它之后我们还将进一步…

HBase的简单学习三

一 过滤器 1.1相关概念 1.过滤器可以根据列族、列、版本等更多的条件来对数据进行过滤&#xff0c; 基于 HBase 本身提供的三维有序&#xff08;行键&#xff0c;列&#xff0c;版本有序&#xff09;&#xff0c;这些过滤器可以高效地完成查询过滤的任务&#xff0c;带有过滤…

Redis中的缓存击穿、缓存穿透、缓存雪崩问题

1.什么是缓存击穿&#xff1f; 客户端恶意访问一个不存在的数据&#xff0c;从而造成穿透缓存&#xff0c;请求直接到达数据库&#xff0c;频繁的发送这一类的请求&#xff0c;直接查询数据库&#xff0c;数据库的压力变大。 1.1如何解决缓存击穿呢&#xff1f; 1&#xff0…

基于harris角点和RANSAC算法的图像拼接matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ....................................................................... I1_harris fu…

【MySQL]】数据库操作指南之数据库的基础操作

&#x1f331;博客主页&#xff1a;青竹雾色间 &#x1f331;系列专栏&#xff1a;MySQL探险日记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 文章目录 1. 创建数据库2.数据库的编码集与校验集2.1 编码集 (Character…

嵌入式Python基础1-2

嵌入式Python基础1-2 条件语句 if elif else 随机数random eval while循环 for循环 水仙花数 循环else list 列表常用方法 增删改查 加排序 append remove pop index() 升序sort(&#xff09;降序 sort(reverseTrue) 反转 reverse&#xff08;&#xff09;…

ESP32开发

1、简介 1.1 种类 WIFI模块在PC上做为客户端、服务器&#xff0c;在STM32上做服务器的通讯。在物联网应用开发有重要作用&#xff0c;种类居多&#xff0c;如下图 红色方框的esp8266-01s型号的无限wifi模块就是本章学习的主要对象。 1.2 特点 小巧的尺寸&#xff1a;ESP-01…

SpanBert学习

SpanBERT: Improving Pre-training by Representing and Predicting Spans 核心点 提出了更好的 Span Mask 方案&#xff0c;也再次展示了随机遮盖连续一段字要比随机遮盖掉分散字好&#xff1b;通过加入 Span Boundary Objective (SBO) 训练目标&#xff0c;增强了 BERT 的性…

python自动生成SQL语句自动化

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python自动生成SQL语句自动化 在数据处理和管理中&#xff0c;SQL&#xff08;Structured …

WAF防范原理

目录 一、什么是WAF 二、纵深安全防御 WAF的组网模式 WAF配置全景 WAF端 服务器 攻击端 拦截SQL注入&#xff0c;XSS攻击&#xff0c;木马文件上传 要求&#xff1a; 使用WAF&#xff0c;通过配置策略要求能防御常见的web漏洞攻击&#xff08;要求至少能够防御SQL、XSS、文…

毕业设计注意事项

1.开题 根据学院发的开题报告模板完成&#xff0c;其中大纲部分可参考资料 2.毕设 根据资料中的毕设评价标准&#xff0c;对照工作量 3.论文 3.1 格式问题 非常重要&#xff0c;认真对比资料中我发的模板&#xff0c;格式有问题&#xff0c;答辩输一半&#xff01; 以word…

wireshark RTP分析参数

主要看丢弃和Delta&#xff0c; 丢弃就是丢掉的udp包&#xff0c;所占的比率 Delta是当前udp包接收到的时间减去上一个udp包接收到的时间 根据载荷可以知道正确的delta应该是多少&#xff0c;比如G711A&#xff0c;ptime20&#xff0c;那么delta理论上应该趋近于20. 这里的de…

C++面向对象程序设计 - 运算符重载

函数重载就是对一个已有的函数赋予新的含义&#xff0c;使之实现新的功能。因此一个函数名就可以用来代表不同功能的函数&#xff0c;也就是一名多用。运算符也可以重载&#xff0c;即运算符重载&#xff08;operator overloading&#xff09;。 一、运算符重载的方法 运算符重…