zoukankan      html  css  js  c++  java
  • C# 8.0 宝藏好物 Async streams

    之前写《.NET gRPC 核心功能初体验》,利用gRPC双向流做了一个打乒乓的Demo,存储消息的对象是IAsyncEnumerable<T>,这个异步可枚举泛型接口支撑了gRPC的实时流式通信。

    本文我将回顾分享

    • foreach/yield return/async await语法糖的本质
    • 如何使用异步流
    • 消费异步流时 附加探索

    foreach/ yield return/async await的本质

    .NET诞生之初,就通过IEnumerable、IEnumerator提供迭代能力,
    前者代表具备可枚举的性质,后者代表可被枚举的方式。
    (看你骨骼惊奇,再送你一本《2021年了,IEnumerableIEnumerator接口还傻傻分不清楚?》)
    如果你真的使用强类型IEnumerable/IEnumerator来产生/消费可枚举类型,会发现要写很多琐碎代码。

    C#推出的yield return迭代器语法糖,简化了产生可枚举类型的编写过程。(编译器将yield return转换为状态机代码来实现IEnumerable,IEnumerator)

    yield 关键字可以执行状态迭代,并逐个返回枚举元素,在返回数据时,无需创建临时集合来存储数据。

    C#foreach语法糖,简化了消费可枚举类型的编写过程。(编译器将foreach抓换为强类型的方法/属性调用)

    IEnumerable src = ...;
    IEnumerator e = src.GetEnumerator();
    try
    {
      while (e.MoveNext()) Use(e.Current);
    }
    finally { if (e != null) e.Dispose(); }
    

    .NET Framework4引入Task,.NET Framework 4.5/C#5.0引入了await/async异步编程语法糖,简化了异步编程的编程过程。(编译器将await/async语法糖转换为状态机,产生Task并在内部回调)

    ☺️以上也看出微软为帮助我们更快速优雅地编写代码,给了很多糖,编译器做了很多事情。

    C#提供了迭代、异步的快捷方式,能否将两者结合?
    两者结合的效果就是: 希望在数据就绪时,接受并处理数据,但不会以阻塞CPU的sing是等待,这在lot流式数据中很常见,

    异步迭代

    有一只爬虫要通过列表页上的链接,抓取链接背后的html内容并显示。

    这是一个[相互独立的长耗时行为的集合(假设分别耗时5,4,3,2,1s)],
    我们使用C#8.0异步可枚举类型IAsyncEnumerable,异步产生/消费枚举元素。

    与同步版本IEmunerable类似,IAsyncEnumerable也有对应的IAsyncEnumerator迭代器,迭代器的实现过程决定了消费的顺序。

    C#8.0 Asynchronous streams

    C#8.0中一个重要的特性是异步流(async stream), 可以轻松创建和消费异步枚举。

    返回异步流的方法特征:

    • async修饰符声明
    • 返回IAsyncEnumerable<T>对象
    • 方法包含yield return语句,用来异步持续返回元素
    static async Task Main(string[] args)
    {
          Console.WriteLine(DateTime.Now + $"	ThreadId:{Thread.CurrentThread.ManagedThreadId}
    ");
    
          await foreach (var html in FetchAllHtml())
          {
               Console.WriteLine(DateTime.Now + $"	ThreadId:{Thread.CurrentThread.ManagedThreadId}	" + $"	output:{html}");
          }
          Console.WriteLine("
    " + DateTime.Now + $"	ThreadId:{Thread.CurrentThread.ManagedThreadId}	");
          Console.ReadKey();
     }
    
     static async IAsyncEnumerable<string> FetchAllHtml()
     {
        for (int i = 5; i >= 1; i--)
        {
            var html = await Task.Delay(i* 1000).ContinueWith((t,i)=> $"html{i}",i);    //  模拟长耗时
            yield return html;
        }
     }
    

    for循环结合yield关键字,决定了IAsyncEnymerator的实现;
    以上代码将使得await foreach消费异步枚举时, 采用与for循环一样的顺序,也就是产生异步任务的先后顺序

    以上不会等待15s然后一股脑抛出所有数据,而是根据枚举for循环,一次就绪,依次显示,总耗时还是15s,只不过每一步都是异步的。

    附加思考:实现一个更有意思的迭代器

    ☺️ 但是我内心想,能不能按照完成异步任务的顺序,先完成先消费,这难道不是人之常情,交互体验应该更好。

    static async IAsyncEnumerable<string> FetchAllHtml()
    {  
        var tasklist= new List<Task<string>>();
        for (int i = 5; i >= 1; i--)
        {
           var t= Task.Delay(i* 1000).ContinueWith((t,i)=>$"html{i}",i);      // 模拟长耗时任务
           tasklist.Add(t);
        }
        while(tasklist.Any())  
        {
          var tFinlish = await Task.WhenAny(tasklist);
          tasklist.Remove(tFinlish); 
          yield return await tFinlish;
        }
    }  
    

    上面我先构造了可等待的任务列表,通过Task.WhenAny()按照任务完成的顺序 返回迭代。

    以上总耗时取决于 耗时最长的那个异步任务5s.


    .NETCore 3.1 已经可以在webapi中使用异步流,意味着我们可将流式数据返回到HTTP响应。

    前端也已经有试验性的Streams API可以对接消费流式数据。
    传送门: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API
    浏览器兼容列表: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API#browser_compatibility

    对于web应用,这着实能提高 可交互性:
    想象之前含多个长耗时行为的列表数据,现在不必等待所有数据,,配以loading,谁家完成谁加载,效果杠杠。

  • 相关阅读:
    Window 窗口类
    使用 Bolt 实现 GridView 表格控件
    lua的table库
    Windows编程总结之 DLL
    lua 打印 table 拷贝table
    使用 xlue 实现简单 listbox 控件
    使用 xlue 实现 tips
    extern “C”
    COleVariant如何转换为int double string cstring
    原来WIN32 API也有GetOpenFileName函数
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/14574508.html
Copyright © 2011-2022 走看看