目录

这些是什么?

晦涩的语言功能:认识真实世界

将内容捕获为流?

异步消费

异步限制

又一陷阱

结论


我用一个新的语言特性解决一个困难的性能问题的旅程:异步流。解释什么是异步流,并展示了它们可以解决的现实世界问题。

最近我遇到了一个有趣的性能挑战,最终3小时的工作减少到1.5小时,这要归功于最近的C#语言增强功能:异步流。哇,所以2019年我听到你说。我敢肯定您已经阅读了一些标题并浏览了一些博客文章,但是您真的了解这项技术及其含义吗?我没有。在这篇文章中,我将快速解释什么是异步流,描述它们帮助我解决了哪些现实世界的问题,并展示了一些常见的陷阱,以防您遇到类似的情况。

这些是什么?

简而言之,异步流是C# 8中引入的一种语言功能,它允许您以异步方式处理数据流。对,很明显。一个例子会有所帮助。

IAsyncEnumerable<int> numbers = Producer.GetNumbersAsync();
await foreach (var number in numbers)
{if (number > 10) break;
}

上面我们检索了一组IAsyncEnumerable类型的数字(C# 8中引入的接口),并使用await foreach(C# 8中也引入的新语言功能)迭代其中的前10个。

这里的奇特之处在于,循环的每次迭代都有一个隐藏的等待,它创建一个延续并将控制权返回给调用者,直到数据提供者有一个新的数字要提供。将控制权返回给调用者通常是C# 5中引入的await所做的。它释放了主机来刷新移动应用程序的UI或响应HTTP请求。IAsyncEnumerable的新功能是当谈到可枚举时, await现在是一等公民。

如果您在ILSpy中打开前面示例中的代码,您可以看到它是如何工作的。如果您反编译并将其视为C# 8之前的版本(ILSpy在这种情况下非常棒)。

然后你最终得到这个:

IAsyncEnumerable<int> numbers = Producer.GetNumbersAsync();
IAsyncEnumerator<int> asyncEnumerator = numbers.GetAsyncEnumerator();
try
{while (await asyncEnumerator.MoveNextAsync()){int number = asyncEnumerator.Current;if (number > 10){break;}}
}
finally
{if (asyncEnumerator != null){await asyncEnumerator.DisposeAsync();}
}

现在您可以清楚地看到IAsyncEnumerable的工作原理几乎与IEnumerable对它的.MoveNext和.Current方法所做的完全一样。除了三件事:

  1. 方法名称后缀为Async
  2. 一切都是基于任务的
  3. 正在进行一些额外的清理工作

有趣,但如何有用还有待观察。

晦涩的语言功能:认识真实世界

在我的项目中,我们需要每天下载和处理大文件。想想60 Gig CSV文件。从技术上讲,它们是60 Gig BSON文件。如果它们是CSV,它们会更大。无论如何,关键是我们需要读取和处理大量数据,而且速度很慢。这需要几个小时。这是一个问题,因为数据需要在一天中的某个时间准备好,如果出现问题,我们必须重新开始。所以我们只得到了几个镜头,更糟糕的是:未来这个客户的数据会变得更大。我们需要找到性能优化。

现在,从历史上看,我们分几个步骤处理这个过程,例如:

  1. 下载文件
  2. 读取和处理文件(使用Task Parallel Library中的DataFlow,如果你不熟悉它,你应该放下一切去学习)
  3. 将结果(仅约90兆)插入数据库

这很简单,但总的来说这三个步骤花了2个多小时。下载:~40分钟。阅读和处理:~1.5小时。插入:~10分钟。

该团队花了很多时间集思广益地解决性能问题。但是有一件事情让我对这个过程感到不安。也许重新阅读要点,看看是否有任何内容站得住脚。

答:为什么我们要保存到磁盘并从磁盘读取?!理论上这就是流存在的原因。我们应该能够下载数据并将其处理成90兆,并且根本不会命中磁盘。对?!

此外,该IO听起来很慢,但这是另一回事。

将内容捕获为流?

但是我不知道异步流是否可以应用于通过HTTP下载大文件。首先,团队一直在下载BSON中的zip文件。我需要将数据作为流消耗,所以压缩是正确的。最终证明将BSON作为流使用是可行的,但后来才出现,超出了本文的范围。因此,第一遍解压缩了CSV。

幸运的是,有一种方法可以在数据提供者的API中指定我们想要解压缩的CSV内容。这会增加下载时间,但我打赌我们会在处理过程中弥补它,因为磁盘似乎是一个瓶颈。

接下来我很好奇TCP数据包是否根据请求立即启动并在换行边界处中断。重要的?不确定,虽然它确实为博客文章制作了一张好照片。

Wireshark数据包如下所示:

换句话说,这是一个数据包:

`U0(ñòßGAäwÆMP'Û10d
2021-06-26T23:24:45,10.79
2021-06-26T23:24:53,97.83
2021-06-26T23:25:01,86.53
2021-06-26T23:25:09,3.83
2021-06-26T23:25:17,39.38
2021-06-26T23:25:25,37.94
2021-06-26T23:25:33,31.59
2021-06-26T23:25:41,12.55
2021-06-26T23:25:49,74.67
2021-06-26T23:25:57,95.25

顶部有一些随机元数据,但以换行符结尾。

顺便说一下,这实际上是我构建的一个应用程序的结果,该应用程序是为了这篇博文的目的来模拟我们的实际数据提供者。它被称为DisklessAsynchronousStreams(也许不要试图说快10倍)。如果您想更详细地探索这篇文章的代码,它是开源的。

异步消费

再回到这一点,我很快就学会了拉异步数据没有写入到磁盘中的重要法宝是在HttpClient上调用GetAsync()或SendAsync()时设置HttpCompletionOption.ResponseHeadersRead标志。这告诉编译器只在收到头文件之前阻塞,然后继续执行。然后可以在数据仍在下载时调用ReadLineAsync()。进一步来说:

using var response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(stream, Encoding.UTF8);
while (!streamReader.EndOfStream)
{var line = await streamReader.ReadLineAsync();var trade = GetTradeFromLine(rowNum, line);yield return trade;
}

上面的代码有效,但只是因为C# 8。在C# 8之前,返回类型需要是async Task<IEnumerable<Trade>> 。似乎有道理。除了,编译器然后给你:

The return type of an async method must be void, Task, Task<T>, a task-like type, IAsyncEnumerable<T>, or IAsyncEnumerator<T>

使用C# 8和IAsyncEnumerable的简单解决方案是返回IAsyncEnumerable,然后可以使用async foreach使用它。

private async IAsyncEnumerable<trade> StreamReadLines()
{...
}</trade>

ps 如果你对yield的工作原理不满意,请查看System.Linq.Where()的真正工作原理

异步限制

这是一个有趣的错误,猜猜它是什么意思:

类型System.IO.IOException

消息:无法从传输连接读取数据:远程主机强行关闭了现有连接。

内部异常:

类型:System.Net.Sockets.SocketError

SocketErrorCode : 连接重置

消息:现有连接被远程主机强行关闭。

堆栈跟踪:

...

System.IO.StreamReader.d__67.MoveNext()

System.IO.StreamReader.d__59.MoveNext

如果您说远程主机关闭了我们的连接,恭喜您可以阅读,但遗憾的是,事实并非如此。实际问题是消费者超出了缓冲区(这发生在我们中最好的人身上),然后.NET欺骗了我们,这让我们感到难过。

当消费者从生产者读取数据太慢时就会出现问题。基本上,如果数据进入的速度比我们处理它的速度快,那么有人需要将该数据保存在特定大小的内存插槽中,最终数据将超过插槽的大小。

有趣的是,您可以通过在HttpClient上设置较小的MaxResponseContentBufferSizeon值使其更快发生。不幸的是,您不能设置MaxResponseContentBufferSize超过2 Gig的默认大小。因此,请确保在主消息处理循环中不要做任何缓慢的事情。

又一陷阱

不要指望消费者在Fiddler打开的情况下成功地异步传输读取数据。Fiddler非常适合观察常规的HTTP流量,但它在转发之前将整个请求分批处理,接下来你知道你浪费了30分钟试图弄清楚为什么你不能在一个重复的项目上复制你的生产环境,同时写一篇博客文章。注意我的警告:不要成为那个人。

结论

太好了,所以我停止将数据保存到磁盘,但大大增加了我的下载大小。它值得吗?幸运的是,我很高兴地发现批处理时间减少了50%。此外,它消耗更少的内存和CPU以及电力和冷却成本。您的结果可能会有所不同。

说起来:这段代码更难维护,所以要谨慎使用。但是无磁盘异步流是一项很好的技术,您可以了解是否找到了正确的问题。

https://www.codeproject.com/Articles/5307011/Stop-Saving-to-Disk-with-Csharp-Asynchronous-Strea

停止使用C#异步流保存到磁盘相关推荐

  1. 【Kotlin 协程】Flow 异步流 ⑤ ( 流的上下文 | 上下文保存 | 查看流发射和收集的协程 | 不能在不同协程中执行流的发射和收集操作 | 修改流发射的协程上下文 | flowOn函数 )

    文章目录 一.流的上下文 1.上下文保存 2.流收集函数原型 3.流发射函数原型 4.代码示例 - 查看流发射和收集的协程 5.代码示例 - 不能在不同协程中执行相同流的发射和收集操作 二.修改流发射 ...

  2. postgresql主从备份_基于PG12.2实现主从异步流复制及主从切换教程(下)

    概述 今天主要介绍如何搭建PG主从流复制及主从切换,仅供参考. PS:上篇的地址在文末链接. PostgreSQL数据库主从异步流复制搭建 环境说明: 1.安装PG数据库(主从库进行) 用脚本进行,略 ...

  3. Linux文件系统保存在哪里,文件系统保存在磁盘的()

    [名词&注释] 文件系统(file system).命令行(command line).文件描述符(file descriptor).路径名(path name).第一个(first).返回值 ...

  4. corda_Corda服务的异步流调用

    corda 如何使流程更快? 如果您与Corda合作已有一段时间,那么您很有可能已经考虑过这一点. 您可以通过以下几方面进行合理的调整以提高性能:事务大小,优化查询并减少整个Flow执行过程中所需的网 ...

  5. Corda服务的异步流调用

    如何使流程更快? 如果您已经与Corda合作了一段时间,那么您很有可能已经考虑过这一点. 您可以通过以下几方面进行合理的调整来提高性能:事务大小,优化查询并减少整个Flow执行过程中所需的网络跃点数. ...

  6. CentOS下PostgreSQL 主从实现之异步流复制(Hot Standby)

    Standby数据库原理 简单介绍一些基础概念与原理,首先我们做主从同步的目的就是实现db服务的高可用性,通常是一台主数据库提供读写,然后把数据同步到另一台从库,然后从库不断apply从主库接收到的数 ...

  7. 5、异步流(Asynchronous Flow)

    异步流(官方文档) 目录 1.多个值的表示 1.1.使用Sequences 1.2.使用suspend函数 1.3.使用Flow 2.Flow是延迟执行的 3.取消flow的基本使用 4.flow构建 ...

  8. 【Kotlin 协程】Flow 异步流 ② ( 使用 Flow 异步流持续获取不同返回值 | Flow 异步流获取返回值方式与其它方式对比 | 在 Android 中使用 Flow 异步流下载文件 )

    文章目录 一.使用 Flow 异步流持续获取不同返回值 二.Flow 异步流获取返回值方式与其它方式对比 三.在 Android 中 使用 Flow 异步流下载文件 一.使用 Flow 异步流持续获取 ...

  9. 海康萤石摄像头本地局域网拉流保存

    海康萤石的视频cp1型号,获取局域网的视频流:rtsp 拉流方法 首先需要在软件中打开rtsp开关,然后使用nmap工具测试下摄像头开放的端口号,一定会有一个554端口开放 $ nmap 192.16 ...

最新文章

  1. VIAT——虫洞星辰大海里程碑
  2. nagios部署安装中篇
  3. k3note Android8,联想乐檬K3 Note官方稳定版 最新VIBE刷机包 精简优化 完美加入Root权限...
  4. 聊聊RocksDB Compact
  5. 第5次基金申请终于中了!这个血泪教训一定要避免
  6. 软著文档鉴别材料_软著申请被驳回补正材料期限是多久?逾期未补正申请被撤回怎么办?...
  7. Python学习杂记之静态网页学习
  8. 问题 A: 【动态规划】采药_二维数组_一维数组
  9. 行编辑器c语言,行编辑器——C语言.doc
  10. 56. SAMBA 服务器
  11. 国际C 语言乱码大赛(IOCCC )
  12. access h3c交换机光口_h3c交换机端口配置命令行
  13. 服务器的ras性能指标,Memory RAS Configuration
  14. 这45道面试可能被问到的JS判断题!你能答对几道?
  15. css3 如何画太极 和 奥运五环
  16. As的LogCat打开方法
  17. 机器人出魔切还是三相_UZI卡莎五分钟魔切,绝境四杀带领队伍走向胜利?观众:永远滴神...
  18. dw网页设计期末设计一个网页_《网页设计与制作Dreamweaver》期末考试试题
  19. 简单易懂的10折交叉法
  20. 优化理论20---插值法: Hermite插值法、龙格现象、分段插值、样条插值

热门文章

  1. 计算机设置成一个网络,同一个路由器上的电脑怎么设置成局域网连网打 – 手机爱问...
  2. c语言3×3行列互换_戴尔推出UltraSharp U4021QW显示器 采用雷电3接口和5K×2K曲面屏...
  3. c语言400行小游戏,400行代码编C语言控制台界版2048游戏,编写疯子一样的C语言代码...
  4. fgetcsv php,PHP - fgetcsv - 分隔符被忽略?
  5. 使用android开发移动学习平台_移动学习平台有几种开发方法,你造吗?
  6. python8皇后不攻击问题_python 八皇后问题的解法(深度搜索)
  7. 设计灵感|元素拼接的海报到底好看在哪里?
  8. 甜蜜暴击,情人节插画素材,甜而不腻!
  9. 最适合说故事的插画素材,应用到UI设计中,感情线有了!
  10. 质量超高的UI素材站!推荐UI\UX设计师