聊一聊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正式版,可能无法选择3.0的与预览版,听过只有VS 2019 Preview才支持.Net core 3.0的预览版.

总结

我们已经讨论过 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#使用foreach循环遍历数组完整实例

    本文实例讲述了C#使用foreach循环遍历数组的方法.分享给大家供大家参考,具体如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //声明数组. 第一种方法. 声明并分配元素大小. int[] Myint

  • C#中用foreach语句遍历数组及将数组作为参数的用法

    对数组使用 foreach C#提供 foreach 语句. 该语句提供一种简单.明了的方法来循环访问数组或任何可枚举集合的元素. foreach 语句按数组或集合类型的枚举器返回的顺序处理元素,该顺序通常是从第 0 个元素到最后一个元素. 例如,以下代码创建一个名为 numbers 的数组,并使用 foreach 语句循环访问该数组: int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 }; foreach (int i in numbers) { Sy

  • C#使用foreach语句遍历队列(Queue)的方法

    本文实例讲述了C#使用foreach语句遍历队列(Queue)的方法.分享给大家供大家参考.具体如下: using System; using System.Collections; public class QueuesW3 { static void Main(string[] args) { Queue a = new Queue(10); int x = 0; a.Enqueue(x); x++; a.Enqueue(x); foreach (int y in a) { Console.

  • C#使用foreach遍历哈希表(hashtable)的方法

    本文实例讲述了C#使用foreach遍历哈希表(hashtable)的方法.分享给大家供大家参考.具体实现方法如下: using System; using System.Collection; namespace HashSampleApplication1 { class Program { static void Main() { Hashtable hash = new Hashtable(); hashtable[1] = "kaka"; hashtable[2] = &qu

  • C#使用foreach语句遍历二维数组的方法

    本文实例讲述了C#使用foreach语句遍历二维数组的方法.分享给大家供大家参考.具体分析如下: 如果通过for语句循环遍历二维数组需要两重循环才可以,二foreach语句只需要一次可以完全遍历整个二维数组,下面是代码演示 using System; public class w3demo{ public static void Main() { int sum = 0; int[,] nums = new int[3,5]; // give nums some values for(int i

  • C#使用foreach语句遍历堆栈(Stack)的方法

    本文实例讲述了C#使用foreach语句遍历堆栈(Stack)的方法.分享给大家供大家参考.具体如下: using System; using System.Collections; public class StacksW3 { static void Main(string[] args) { Stack a = new Stack(10); int x = 0; a.Push(x); x++; a.Push(x); foreach (int y in a) { Console.WriteL

  • C#使用foreach语句简单遍历数组的方法

    本文实例讲述了C#使用foreach语句简单遍历数组的方法.分享给大家供大家参考.具体如下: using System; public class jb51demo { public static void Main() { int sum = 0; int[] nums = new int[10]; // give nums some values for(int i = 0; i < 10; i++) nums[i] = i; // use foreach to display and su

  • C#中foreach语句使用break暂停遍历的方法

    本文实例讲述了C#中foreach语句使用break暂停遍历的方法.分享给大家供大家参考.具体分析如下: 下面的代码演示了在C#中使用foreach时如何通过break语句暂停数据遍历 using System; public class w3demo { public static void Main() { int sum = 0; int[] nums = new int[10]; // give nums some values for(int i = 0; i < 10; i++) n

  • 聊一聊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这个功能已经发布很久了,在去年

  • C#5.0中的异步编程关键字async和await

    一.Asynchronous methods 异步方法 .NET 4.5 的推出,对于C#又有了新特性的增加——就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程. 使用async修饰的方法被称为异步方法,这个方法调用时应该在前面加上await. 异步方法命名应该以Async结尾,这样大家知道调用的时候使用await. async和await关键字只是编译器的功能,编译器最终会用Task类创建代码. 1.创建返回任务的异步方法 建立一个同步方法Greeting,该方法在

  • 详解.NET Core 3.0中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web.桌面.机器学习.容器.IoT 等的版本 3.0. .NET Core 1 .NET Core 的历史可追溯到几年前,版本 1 是在 2016 年推出,旨在生成第一版开放源代码和跨平台(Windows.macOS 和 Linux)的 .NET.灵感来源于只能使用开放源代码框架的客户,以及需要在 Li

  • vue2.0中vue-cli实现全选、单选计算总价格的实例代码

    由于工作的需要并鉴于网上的vue2.0中vue-cli实现全选.单选方案不合适,自己写了一个简单实用的.就短短的126行代码. <template> <div> <table> <tr> <td><input type="checkbox" v-model="checkAll">全选({{checkedCount}})</td> <td>产品名称</td> &

  • 在ASP.NET 2.0中操作数据之一:创建一个数据访问层

    导言 作为web开发人员,我们的生活围绕着数据操作.我们建立数据库来存储数据,写编码来访问和修改数据,设计网页来采集和汇总数据.本文是研究在ASP.NET 2.0中实现这些常见的数据访问模式之技术的长篇系列教程的第一篇.我们将从创建一个软件框架开始,这个框架的组成部分包括一个使用强类型的DataSet的数据访问层(DAL),一个实施用户定义的业务规则的业务逻辑层(BLL),以及一个由共享页面布局的ASP.NET网页组成的表现层.在打下这个后端的基础工作之后,我们将开始转向报表,示范如何显示,汇总

  • php中使用in_array() foreach array_search() 查找数组是否包含时的性能对比

    判断某字符是否包含与某于数组中,方法有很多,刚学习php的新手们估计偏向于使用循环来解决,对于一般的小网站来说,这种解决方案是不会出现什么大问题的.但就性能来说,这种方法不是最好的方法,下面笔者就 foreach,in_array() array_search 这三种方法来比较这三种方法在性能表现上的差异. <?php $runtime= new runtime; $runtime->start(); $a = 'k'; $b = array('a','b','c','d','e','f','

  • C#7.0中新特性汇总

    以下将是 C# 7.0 中所有计划的语言特性的描述.随着 Visual Studio "15" Preview 4 版本的发布,这些特性中的大部分将活跃起来.现在是时候来展示这些特性,你也告诉借此告诉我们你的想法! C#7.0 增加了许多新功能,并专注于数据消费,简化代码和性能的改善.或许最大的特性就是元祖和模式匹配,元祖可以很容易地拥有多个返回结果,而模型匹配可以根据数据的"形"的不同来简化代码.我们希望,将它们结合起来,从而使你的代码更加简洁高效,也可以使你更加

  • 深入理解java中for和foreach循环

    •for循环中的循环条件中的变量只求一次值!具体看最后的图片 •foreach语句是java5新增,在遍历数组.集合的时候,foreach拥有不错的性能. •foreach是for语句的简化,但是foreach并不能替代for循环.可以这么说,任何foreach都能改写为for循环,但是反之则行不通. •foreach不是java中的关键字.foreach的循环对象一般是一个集合,List.ArrayList.LinkedList.Vector.数组等. •foreach的格式: for(元素类

  • asp.net 2.0 中的URL重写以及urlMappings问题

    在asp.net2.0中的urlMappings倒是非常好用,可惜暂不支持正则表达式,不过,好在如果用IHttpModule的话 不管什么样的请求都会先经过IHttpModule这样就为URL重写提供了一个好机会: 下面是我写的一个IHttpModule: using System;  using System.Web; public class ReWriteModule:IHttpModule  {  public ReWriteModule()  {  }  public override

  • 在Framework 4.0中:找出新增的方法与新增的类(二)

    问题描述:在Framework 4.0中:找出新增的方法与新增的类(一) 为什么动态加载程序集无法找出Framework 4.0 和Framwork2.0 新增的方法和类? 因为控制台程序默认就添加了Framework4.0的程序集,当你使用Object,Type,string这些类的时候就已经在使用已经加载的程序集了,而clr不会重复的去加载程序集??,这点记不清了.所以V2Assembly 和v4Assembly都是Framework4.0的Assembly. 验证: 复制代码 代码如下:

随机推荐