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

C#----异步编程

1 多线程和异步的区别

多线程 ≠ 异步

  1. 多线程经常会阻塞,而异步不阻塞
  2. 多线程和异步的使用场景不同

1.1 多线程使用场景

  • 适合CPU密集型操作

  • 适合长期运行的任务
    多线程适合比较耗时的任务,或者需要长期运行的任务,它会充分调动CPU的某一个核去进行相应的计算。使用多线程很多的一个场景就是,在后台开一个轮询。

  • 线程的创建销毁开销比较大
    异步默认借助线程池,线程池的开销就小很多。

  • 提供更底层的控制,操作线程,锁,信号
    线程是一个比较底层的功能,所以提供了更底层的控制,比如线程锁,信号量等等,但是异步其实对于线程池的封装,这样就不能很好地进行底层的操作。

  • 线程不易于传参及返回
    传参的话可以在Thread.Start里面去传参,但是如果你希望它返回一个结果,那就有点麻烦了,你可能不得不借助全局变量,或者回调函数,同时你还需要在某些地方去阻塞,从而等待某个线程的结束,这样才能保证那个线程已经计算出了结果。

  • 线程的代码书写较为繁琐
    尤其是当你任务比较多,任务之间可能还有一些顺序先后关系,这种情况下你的代码可能
    会显得非常繁琐,但是如果采用异步编程,使用async await 可以非常灵活的去实现这些功能。

1.2 异步使用场景

  • 适合IO密集型操作

  • 适合短暂的小任务
    若频繁访问IO操作,这种情况下比较适用异步编程,因为它比较适合短暂且频繁的小任务,而不是使用多线程。因为线程开出来后,你肯定是希望它多干一会活,毕竟线程的创建开销比较大。但是如果只是一个几十,几百毫秒的小任务,这时候使用多线程就显得不划算了。

  • 避免线程阻塞,提高系统的响应能力
    更重要的是,它不是线程,它不会阻塞线程,所以它可以提高系统的响应能力。一种使用场景是,在UI线程上调用一个方法,若这个方法阻塞了,则UI就会卡顿,这种情况下就需要使用异步。

2 异步任务

2.1 Task 是一个包含异步任务状态的引用类型

task.Id.ToString(); // 获取任务的唯一标识符task.IsFaulted.ToString(); // 获取一个值,该值指示任务是否已因未处理的异常而完成task.IsCanceled.ToString(); // 获取一个值,该值指示任务是否已被取消task.IsCompleted.ToString(); // 获取一个值,该值指示任务是否已完成task.Exception?.ToString(); // 获取导致任务失败的未处理异常的聚合task.CreationOptions.ToString(); // 获取任务的创建选项task.Status.ToString(); // 获取任务的当前状态

2.1 Task 是对于异步任务的抽象

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

2.3 任务的结果

1.返回值为Task 表示异步任务没有返回值

  1. 返回值为Task 则表示异步任务有类型为T的返回值

3 异步方法 (Async Task)

3.1 将异步方法标记为async 之后,就可以在方法中使用await 关键字

await XXX 这个XXX 可以是 Task 也可以是 async Task 类型。

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

await 前后会切换线程

若不想切换线程可使用 Task 的 ConfigureAwait(true); //这里配置ConfigureAwait为true,可以回到之前的线程

你想回到之前的线程,你必须得有一个同步上下文。但是对于一个控制台应用程序,它默认是没有上下文的。只有Windform WPF这些程序,也就是UI线程,它默认是有同步上下文的。因为它要保证UI线程需要拥有UI线程上的全部的资源,比如按钮,TextBox这些东西,任何的这些操作都需要发生在UI线程上,而控制台程序就没有这些要求了。
所以,这里总的来说await关键字会等待异步任务的结束,并获得结果,这里的await Task.Delay(1000)就相当于一个异步任务,但是这里是没有返回值的。再比如我们看下面这个例子:

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

3.4 Async Task

  1. 返回值依旧是Task 类型 但是在其中可以使用 await 关键字
  2. 在其中写返回值可以直接写Task 中返回类型
public Task Run1()
{return Task.Delay(1000);  // 必须返回Task
}public async Task Run2()
{await Task.Delay(1000);    // 没有返回值 
}
public async Task<string> Run3()
{await Task.Delay(1000);    // 没有返回值 return "Hello";  // 返回值
}

3.4 Async void

  1. 同样是状态机,但缺失记录状态的Task 对象
    Task 状态机是 AsyncTaskMethodBuilder 现在是 AsyncVoidMethodBuilder
    async void 方法里面可以使用await, 但是调用的地方没办法使用await ,因为它的返回值是void,不是Task 。所以也没办法知道异步任务的返回值和状态。
public async void Run4()
{await Task.Delay(1000);    // 没有返回值 
}public async void Run5()
{Run4();    // 这里没办法用 await
}
  1. 无法聚合异常(AggregateException), 需谨慎处理异常
    最需要注意的是,Async void 没有办法获取异常任务的报错信息。如果有Task,Task 会把收集异常报错信息做成汇总,就叫做AggregateException 聚合异常,现在没有Task ,就没有办法实现这个功能。报错会直接抛出。

async void 捕获不了异常
在这里插入图片描述
async Task 可捕获到异常

修改成 Run7().wait()
在这里插入图片描述
修改成 Run7().GetAwaiter().GetResult();
在这里插入图片描述
3. 几乎只对于事件的注册
上面提到 async void 这么危险 但 并不是所有的情况下都使用 async task 就可以了呢 ,有一种情况根本用不了 async Task,那就是对于事件的注册,尤其是WPF里面的Click 这些事件。

3.6 异步编程具有传染性

  1. 一处async 处处async
  2. 几乎所有自带的方法都提供了异步的版本
    。。。

4 异步编程的重要思想

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

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

  3. 常见的阻塞情形

  • Task.wait() Task.Result
    若任务没有完成,就会阻塞当前线程,容易造成死锁

  • Task.Delay() Thread.sleep()

  • IO 同步操作的方法

  • 其它繁重且耗时的任务

5 同步上下文

  1. 一中管理和协调线程的机制,允许开发者将代码的执行切换到特点的线程。
    前面异步任务里面线程的切换,都是借助同步上下文来实现的

  2. Winform 和 WPF 具有同步的上下文(UI 线程) ,控制台程序没有

  3. ConfigureAwait(false)

配置人通过await方法结束后是否会回到原来的线程,默认为True,一般只有UI线程会采用这种方式。可以看一下下面代码:

private void Button_Click(object sender, RoutedEventArgs e)
{Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");TestAsync().ConfigureAwait(true);Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
}public async Task TestAsync()
{await Task.Delay(1000);return;
}

打印结果
在这里插入图片描述

若 不回到之前的线程 则前后线程不一致 ,若更改了控件则会报错,因为控件必须在UI线程上进行修改。

private async void Button_Click(object sender, RoutedEventArgs e)
{Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");await TestAsync().ConfigureAwait(false);this.btn.Content = "完成";Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
}public async Task TestAsync()
{await Task.Delay(1000);return;
}

在这里插入图片描述
还要注意一个地方:

int result = TestAsync().Result; // 容易造成死锁

 private async void Button_Click(object sender, RoutedEventArgs e){Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");int result = TestAsync().Result;this.btn.Content = "完成";Debug.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");}public async Task<int> TestAsync(){await Task.Delay(1000);return 20;}

这里调用.Result阻塞了UI线程,然后TestAsync()方法里面,await Task.Delay(2000);之后希望回到UI线程,可是现在UI线程被阻塞,它需要等异步任务执行完才会释放,所以导致了死锁。解决方法为:
async Task TestAsync()
{
await Task.Delay(2000).ConfigureAwait(false);
return 10;
}

异步任务 需要 UI线程 但UI线程现在处于阻塞状态
UI线程需要 异步任务执行完成 才会释放

5 TaskScheduler

TaskScheduler也可以用来配置同步上下文,控制Task的调度和运行线程,还有设置优先级,上下文及执行状态等。

6. 异步任务的创建

  1. Task.Run()

  2. Task.Factory.StartNew()

Task.Factory.StartNew()相当于Task.Run()的完整版,提供了比Task.Run()更多的功能,比如TaskCreationOptions.LongRunning标记它是一个长时间运行的线程

  1. new Task + Task.Start()
    跟new Thread 和 Thread.Start很像

文章转载自:

http://o6jcvOVq.sfsjh.cn
http://dgb4W0mo.sfsjh.cn
http://nf05s2zc.sfsjh.cn
http://xh5KpSNy.sfsjh.cn
http://uYePda9l.sfsjh.cn
http://ILVefdUV.sfsjh.cn
http://9GDV1DcW.sfsjh.cn
http://BFK51JQR.sfsjh.cn
http://nhs2vjoF.sfsjh.cn
http://LFvLVZsz.sfsjh.cn
http://AUDmw8tc.sfsjh.cn
http://8LPuaIyN.sfsjh.cn
http://bnKojhAN.sfsjh.cn
http://d0t4vw31.sfsjh.cn
http://Febj5o1a.sfsjh.cn
http://QTgbLF7o.sfsjh.cn
http://JNKsYDxz.sfsjh.cn
http://zwllNsUA.sfsjh.cn
http://tv6LYRmM.sfsjh.cn
http://zL37gqYb.sfsjh.cn
http://mr9mzRii.sfsjh.cn
http://g2xUOsgD.sfsjh.cn
http://XkQoOnHi.sfsjh.cn
http://f2EWW9n3.sfsjh.cn
http://auIpK9E3.sfsjh.cn
http://TzsM6m8Y.sfsjh.cn
http://2pp0tgRg.sfsjh.cn
http://dvAJlQMy.sfsjh.cn
http://pYAPtmux.sfsjh.cn
http://Xsymxarm.sfsjh.cn
http://www.dtcms.com/a/363316.html

相关文章:

  • 基于Django的论坛系统设计与实现(代码+数据库+LW)
  • Qt模型/视图编程详解:QStringListModel与多视图数据同步
  • 链表题类型注解解惑:理解Optional,理解ListNode
  • 前端实现解析【导入】数据后调用批量处理接口
  • GaussDB 等待事件为LockMgrLock处理方法
  • 为什么程序员总是发现不了自己的Bug?
  • flutter踩坑插件:Swift架构不兼容
  • 疯狂星期四文案网第58天运营日记
  • 手撕Redis底层2-网络模型深度剖析
  • 【3D 入门-4】trimesh 极速上手之 3D Mesh 数据结构解析(Vertices / Faces)
  • Valkey vs Redis详解
  • 基于若依框架开发WebSocket接口
  • 计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统
  • 【交易系统系列36】揭秘币安(Binance)技术心脏:从公开信息拼凑“MatchBox”撮合引擎架构
  • 海康摄像头开发---标准配置结构体(NET_DVR_STD_CONFIG)
  • End-To-End 之于推荐-kuaishou OneRec2 笔记
  • css中 ,有哪些⽅式可以隐藏页⾯元素? 区别?
  • 03_网关ip和端口映射(路由器转发)操作和原理
  • Telnet 原理与配置
  • 基于STM32单片机智能家居wifi远程监控系统机智云app设计
  • Replit在线编程工具:支持多语言环境免配置与实时协作,助力编程学习调试与社区项目复用
  • Spring Security的@PreAuthorize注解为什么会知道用户角色?
  • 0902 C++类的匿名对象
  • Nano Banana 复刻分镜,多图结合片刻生成想要的视频
  • 适配第一性原理与分子动力学研究的高性能工作站解析
  • 信息安全各类加密算法解析
  • LDR6600:2C1A适配器协议方案芯片
  • 综合诊断板CAN时间戳稳定性测试报告8.28
  • Nek La Vita È DJ大来 安装教程 简单步骤 教你怎么装(附安装包下载)
  • Windows 命令行:父目录与子目录