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的更多相关文章

  1. C#8.0中的 await foreach

    AsyncStreamsInCShaper 8.0 C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>> sync Streams这个功能已经 ...

  2. 小心C# 5.0 中的await and async模式造成的死锁

    平时在使用C# 5.0中的await and async关键字的时候总是没注意,直到今天在调试一个ASP.NET项目时,发现在调用一个声明为async的方法后,程序老是莫名其妙的被卡住,就算声明为as ...

  3. [转]小心C# 5.0 中的await and async模式造成的死锁

    原文链接 https://www.cnblogs.com/OpenCoder/p/4434574.html 内容 UI Example Consider the example below. A bu ...

  4. VS2015 C#6.0 中的那些新特性(转载)

    自动属性初始化 (Initializers for auto-properties) 以前我们是这么写的 为一个默认值加一个后台字段是不是很不爽,现在我们可以这样写 只读属性的初始化(Getter-o ...

  5. C#6.0 中的那些新特性

    C#6.0 中的那些新特性 前言 VS2015在自己机器上确实是装好了,费了老劲了,想来体验一下跨平台的快感,结果被微软狠狠的来了一棒子了,装好了还是没什么用,应该还需要装Xarmain插件,配置一些 ...

  6. [译] C# 5.0 中的 Async 和 Await (整理中...)

    C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 V ...

  7. [C#] .NET4.0中使用4.5中的 async/await 功能实现异

    好东西需要分享 原文出自:http://www.itnose.net/detail/6091186.html 在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framew ...

  8. C#同步,异步的理解,包括5.0中await和async(学习笔记)

    之前在工作中一直用的是同步线程,就是先进入画面的load事件,然后在里面进行数据库调用的处理.后面又遇到了公司软件中一些比较古老的代码,一开始在那块古老代码中增加机能的时候,我想用到数据库的数据给画面 ...

  9. [C#] .NET4.0中使用4.5中的 async/await 功能实现异步

    在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framework 4.0中却无法使用.这时不免面临着抉择,到底是升级整个解决方案还是不使用呢? 如果你的软件还没发布出去 ...

随机推荐

  1. JavaScript(二、BOM 浏览器对象模型)

    一.BOM是什么 BOM是browser object model的缩写,简称浏览器对象模型 BOM提供了独立于内容而与浏览器窗口进行交互的对象 由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心 ...

  2. HTML5总结

    HTML的定义 HyperText Markup Language 超文本标记语言 超级文本标记语言是标准通用标记语言下的一个应用,也是一种规范,一种标准, 它通过标记符号来标记要显示的网页中的各个部 ...

  3. VM虚拟机链接克隆及linux eth0网卡的快速设置方法

    对于后台开发者来说,在学习过程中必然接触众多中间件,在自己的虚拟机进行操作甚至搭建cluster是很常见的事情. 我在初学者时摸索出一套快速的克隆虚拟机方法.分享给大家. 产品VMware® Work ...

  4. 工作中常用的JS函数整理分享(欢迎大家补充)

    今年在渣X工作整理的常用JS函数 今年来了渣X工作,我所在这个部门分工很奇怪,CSS竟然有专门的人在搞,开发PHP的人员需要处理JS,以至于有时候开发起来不是那么得心应手,感觉把JS和CSS拆开就像是 ...

  5. 在线引用js资源积累

    [jQuery]https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js[Vue]https://cdnjs.cloudfla ...

  6. java并发之CyclicBarrier

    一.CyclicBarrier简述 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互 ...

  7. Linux内核编程、调试技巧小集

    1. 内核中通过lookup_symbol_name获取函数名称 内核中很多结构体成员是函数,有时可能比较复杂不知道具体使用哪一个函数.这是可以通过lookup_symbol_name来获取符号表名称 ...

  8. Linux kernel的中断子系统之(九):tasklet

    返回目录:<ARM-Linux中断系统>. 总结: 二介绍了tasklet存在的意义. 三介绍了通过tasklet_struct来抽想一个tasklet,每个CPU维护一个tasklet链 ...

  9. 解决Ubuntu系统下的VMware Workstation无法打开虚拟网络编辑器界面的问题

    本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=630 操作环境: Ubuntu 17 VMware 14 pro for Linux 问题描述: 我在Ubuntu ...

  10. 部署:持续集成(CI)与持续交付(CD)——《微服务设计》读书笔记

        系列文章目录:     <微服务设计>读书笔记大纲 一.CI(Continuous Integration)简介  CI规则1:尽量频繁地把代码签入到分支中以进行集成 CI规则2: ...