聊一聊C# 8.0中的await foreach
AsyncStreamsInCShaper8.0
很开心今天能与大家一起聊聊C# 8.0中的新特性-Async Streams
,一般人通常看到这个词表情是这样.
简单说,其实就是C# 8.0中支持await foreach
.
或者说,C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>>
.
好吧,还不懂?Good,这篇文章就是为你写的,看完这篇文章,你就能明白它的神奇之处了.
为什么写这篇文章
Async Streams
这个功能已经发布很久了,在去年的Build 2018 The future of C#就有演示,最近VS 2019发布,在该版本的Release Notes中,我再次看到了这个新特性,因为对异步编程不太熟悉,所以借着这个机会,学习新特性的同时,把异步编程重温一遍.
本文内容,参考了Bassam Alugili
在InfoQ中发表的Async Streams in C# 8,撰写本博客前我已联系上该作者并得到他支持.
Async / Await
C# 5 引入了 Async/Await,用以提高用户界面响应能力和对 Web 资源的访问能力。换句话说,异步方法用于执行不阻塞线程并返回一个标量结果的异步操作。
微软多次尝试简化异步操作,因为 Async/Await 模式易于理解,所以在开发人员当中获得了良好的认可。
详见The Task asynchronous programming model in C#
常规示例
要了解问什么需要Async Streams
,我们先来看看这样的一个示例,求出5以内的整数的和.
static int SumFromOneToCount(int count)
{
ConsoleExt.WriteLine("SumFromOneToCount called!");
var sum = 0;
for (var i = 0; i <= count; i++)
{
sum = sum + i;
}
return sum;
}
调用方法.
static void Main(string[] args)
{
const int count = 5;
ConsoleExt.WriteLine($"Starting the application with count: {count}!");
ConsoleExt.WriteLine("Classic sum starting.");
ConsoleExt.WriteLine($"Classic sum result: {SumFromOneToCount(count)}");
ConsoleExt.WriteLine("Classic sum completed.");
ConsoleExt.WriteLine("################################################");
}
输出结果.
可以看到,整个过程就一个线程Id为1的线程自上而下执行,这是最基础的做法.
Yield Return
接下来,我们使用yield运算符使得这个方法编程延迟加载,如下所示.
static IEnumerable<int> SumFromOneToCountYield(int count)
{
ConsoleExt.WriteLine("SumFromOneToCountYield called!");
var sum = 0;
for (var i = 0; i <= count; i++)
{
sum = sum + i;
yield return sum;
}
}
主函数
static void Main(string[] args)
{
const int count = 5;
ConsoleExt.WriteLine("Sum with yield starting.");
foreach (var i in SumFromOneToCountYield(count))
{
ConsoleExt.WriteLine($"Yield sum: {i}");
}
ConsoleExt.WriteLine("Sum with yield completed.");
ConsoleExt.WriteLine("################################################");
ConsoleExt.WriteLine(Environment.NewLine);
}
运行结果如下.
正如你在输出窗口中看到的那样,结果被分成几个部分返回,而不是作为一个值返回。以上显示的累积结果被称为惰性枚举。但是,仍然存在一个问题,即 sum 方法阻塞了代码的执行。如果你查看线程ID,可以看到所有东西都在主线程1中运行,这显然不完美,继续改造.
Async Return
我们试着将async用于SumFromOneToCount方法(没有yield关键字).
static async Task<int> SumFromOneToCountAsync(int count)
{
ConsoleExt.WriteLine("SumFromOneToCountAsync called!");
var result = await Task.Run(() =>
{
var sum = 0;
for (var i = 0; i <= count; i++)
{
sum = sum + i;
}
return sum;
});
return result;
}
主函数.
static async Task Main(string[] args)
{
const int count = 5;
ConsoleExt.WriteLine("async example starting.");
// Sum runs asynchronously! Not enough. We need sum to be async with lazy behavior.
var result = await SumFromOneToCountAsync(count);
ConsoleExt.WriteLine("async Result: " + result);
ConsoleExt.WriteLine("async completed.");
ConsoleExt.WriteLine("################################################");
ConsoleExt.WriteLine(Environment.NewLine);
}
运行结果.
我们可以看到计算过程是在另一个线程中运行,但结果仍然是作为一个值返回!任然不完美.
如果我们想把惰性枚举(yield return)与异步方法结合起来,即返回Task<IEnumerable,这怎么实现呢?
Task<IEnumerable>
我们根据假设把代码改造一遍,使用Task<IEnumerable<T>>
来进行计算.
可以看到,直接出现错误.
IAsyncEnumerable
其实,在C# 8.0中Task<IEnumerable>这种组合称为IAsyncEnumerable。这个新功能为我们提供了一种很好的技术来解决拉异步延迟加载的问题,例如从网站下载数据或从文件或数据库中读取记录,与 IEnumerable 和 IEnumerator 类似,Async Streams 提供了两个新接口 IAsyncEnumerable 和 IAsyncEnumerator,定义如下:
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator();
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
Task<bool> MoveNextAsync();
T Current { get; }
}
// Async Streams Feature 可以被异步销毁
public interface IAsyncDisposable
{
Task DiskposeAsync();
}
AsyncStream
下面,我们就来见识一下AsyncStrema的威力,我们使用IAsyncEnumerable来对函数进行改造,如下.
static async Task ConsumeAsyncSumSeqeunc(IAsyncEnumerable<int> sequence)
{
ConsoleExt.WriteLineAsync("ConsumeAsyncSumSeqeunc Called");
await foreach (var value in sequence)
{
ConsoleExt.WriteLineAsync($"Consuming the value: {value}");
// simulate some delay!
await Task.Delay(TimeSpan.FromSeconds(1));
};
}
private static async IAsyncEnumerable<int> ProduceAsyncSumSeqeunc(int count)
{
ConsoleExt.WriteLineAsync("ProduceAsyncSumSeqeunc Called");
var sum = 0;
for (var i = 0; i <= count; i++)
{
sum = sum + i;
// simulate some delay!
await Task.Delay(TimeSpan.FromSeconds(0.5));
yield return sum;
}
}
主函数.
static async Task Main(string[] args)
{
const int count = 5;
ConsoleExt.WriteLine("Starting Async Streams Demo!");
// Start a new task. Used to produce async sequence of data!
IAsyncEnumerable<int> pullBasedAsyncSequence = ProduceAsyncSumSeqeunc(count);
// Start another task; Used to consume the async data sequence!
var consumingTask = Task.Run(() => ConsumeAsyncSumSeqeunc(pullBasedAsyncSequence));
await Task.Delay(TimeSpan.FromSeconds(3));
ConsoleExt.WriteLineAsync("X#X#X#X#X#X#X#X#X#X# Doing some other work X#X#X#X#X#X#X#X#X#X#");
// Just for demo! Wait until the task is finished!
await consumingTask;
ConsoleExt.WriteLineAsync("Async Streams Demo Done!");
}
如果一切顺利,那么就能看到这样的运行结果了.
最后,看到这就是我们想要的结果,在枚举的基础上,进行了异步迭代.
可以看到,整个计算过程并没有造成主线程的阻塞,其中,值得重点关注的是红色方框区域的线程5
!线程5
!线程5
!线程5在请求下一个结果后,并没有等待结果返回,而是去了Main()函数中做了别的事情,等待请求的结果返回后,线程5又接着执行foreach中任务.
Client/Server的异步拉取
如果还没有理解Async Streams
的好处,那么我借助客户端 / 服务器端架构是演示这一功能优势的绝佳方法。
同步调用
客户端向服务器端发送请求,客户端必须等待(客户端被阻塞),直到服务器端做出响应.
示例中Yield Return就是以这种方式执行的,所以整个过程只有一个线程即线程1在处理.
异步调用
客户端发出数据块请求,然后继续执行其他操作。一旦数据块到达,客户端就处理接收到的数据块并询问下一个数据块,依此类推,直到达到最后一个数据块为止。这正是 Async Streams 想法的来源。
最后一个示例就是以这种方式执行的,线程5
询问下一个数据后并没有等待结果返回,而是去做了Main()函数中的别的事情,数据到达后,线程5
又继续处理foreach中的任务.
Tips
如果你使用的是.net core 2.2
及以下版本,会遇到这样的报错.
需要安装.net core 3.0 preview
的SDK(截至至博客撰写日期4月9日,.net core SDK
最新版本为3.0.100-preview3-010431
),安装好SDK后,如果你是VS 2019正式版,可能无法选择.net core 3.0
,vs 2019 正式版默认情况下没有开启对预览版.net core 3.0
的支持.
根据网友补充,需要在VS 2019正式版本中需要开启使用 .Net core SDK 预览版
,才能创建3.0的项目.
工具 > 选项 > 项目和解决方案 > .Net Core > 使用 .Net core SDK 预览版
总结
我们已经讨论过 Async Streams
,它是一种出色的异步拉取技术,可用于进行生成多个值的异步计算。
Async Streams
背后的编程概念是异步拉取模型。我们请求获取序列的下一个元素,并最终得到答复。Async Streams 提供了一种处理异步数据源的绝佳方法,希望对大家能够有所帮助。
文章中涉及的所有代码已保存在我的GitHub中,请尽情享用!
https://github.com/liuzhenyulive/AsyncStreamsInCShaper8.0
致谢
之前一直感觉国外的大师级开发者遥不可及甚至高高在上,在遇到Bassam Alugili
之后,我才真正感受到技术交流没有高低贵贱,正如他对我说的 The most important thing in this world is sharing the knowledge!
Thank you,I will keep going!!
参考文献: Async Streams in C# 8 https://www.infoq.com/articles/Async-Streams
聊一聊C# 8.0中的await foreach的更多相关文章
- C#8.0中的 await foreach
AsyncStreamsInCShaper 8.0 C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>> sync Streams这个功能已经 ...
- 小心C# 5.0 中的await and async模式造成的死锁
平时在使用C# 5.0中的await and async关键字的时候总是没注意,直到今天在调试一个ASP.NET项目时,发现在调用一个声明为async的方法后,程序老是莫名其妙的被卡住,就算声明为as ...
- [转]小心C# 5.0 中的await and async模式造成的死锁
原文链接 https://www.cnblogs.com/OpenCoder/p/4434574.html 内容 UI Example Consider the example below. A bu ...
- VS2015 C#6.0 中的那些新特性(转载)
自动属性初始化 (Initializers for auto-properties) 以前我们是这么写的 为一个默认值加一个后台字段是不是很不爽,现在我们可以这样写 只读属性的初始化(Getter-o ...
- C#6.0 中的那些新特性
C#6.0 中的那些新特性 前言 VS2015在自己机器上确实是装好了,费了老劲了,想来体验一下跨平台的快感,结果被微软狠狠的来了一棒子了,装好了还是没什么用,应该还需要装Xarmain插件,配置一些 ...
- [译] C# 5.0 中的 Async 和 Await (整理中...)
C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 V ...
- [C#] .NET4.0中使用4.5中的 async/await 功能实现异
好东西需要分享 原文出自:http://www.itnose.net/detail/6091186.html 在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framew ...
- C#同步,异步的理解,包括5.0中await和async(学习笔记)
之前在工作中一直用的是同步线程,就是先进入画面的load事件,然后在里面进行数据库调用的处理.后面又遇到了公司软件中一些比较古老的代码,一开始在那块古老代码中增加机能的时候,我想用到数据库的数据给画面 ...
- [C#] .NET4.0中使用4.5中的 async/await 功能实现异步
在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framework 4.0中却无法使用.这时不免面临着抉择,到底是升级整个解决方案还是不使用呢? 如果你的软件还没发布出去 ...
随机推荐
- Maven分模块以及打war包
我们如何进行模块化开发呢? 我们使用上面的例子进行演示,先进行合理的优化,我们希望dao和service作为通用的底层工具来使用,把它们合并成一个核心模块(core),build成core.jar,简 ...
- larave5.4自定义公共函数的创建
原文地址:http://blog.csdn.net/qq_38125058/article/details/76862151 公共函数,简单来说就是在任何地方都可以直接使用这个函数.简单介绍两种实现方 ...
- zabbix 3.4 ubuntu 16 用腾讯企业邮箱作为告警邮箱
最近一直在研究zabbix监控系统,今天调试了腾讯企业邮箱作为告警邮箱的设置,本次方式是用内置email组件. 第一步: 选择Administration-->Media Types--> ...
- Runc 简介
RunC 是什么? RunC 是一个轻量级的工具,它是用来运行容器的,只用来做这一件事,并且这一件事要做好.我们可以认为它就是个命令行小工具,可以不用通过 docker 引擎,直接运行容器.事实上,r ...
- Map Reduce和流处理
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由@从流域到海域翻译,发表于腾讯云+社区 map()和reduce()是在集群式设备上用来做大规模数据处理的方法,用户定义一个特定的映射 ...
- linux内核中断之看门狗
一:内核中断 linux内核中的看门狗中断跟之前的裸板的中断差不多,在编写驱动之前,需要线把内核自带的watch dog模块裁剪掉,要不然会出现错误:在Device Drivers /Watchdog ...
- 使用STM32Cube在STM32F7开发板上实现SD+Freertos+Fatfs
简介 最近项目中可能需要使用到SD卡,所以需要对SD卡的配置和使用调研,在配置过程中遇到了一些问题,在此记录一下. STM32Cube配置 Pinout 只需要注意绿色部分的设定 Clock配置 这里 ...
- 新浪微博注册(elenium Python 自动化)
from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom time import sleep ...
- 如何在当前目录下快速打开cmd(或者以管理员的身份打开)
1.在当前目录下,按住shift键+点击右键,选择在此处打开命令窗口 很多时候我们需要打开命令行然后进入到相应目录进行一些操作. 常规的做法是: D:\foo\bar", 然后输入cd 再把 ...
- bzoj 2820 莫比乌斯反演
搞了一整个晚自习,只是看懂了dalao们的博客,目前感觉没有思路-.还是要多切题 next day: 刚才又推了一遍,发现顺过来了,hahaha #include<cstdio> #inc ...