C#8.0: 在 LINQ 中支持异步的 IAsyncEnumerable
C# 8.0中,提供了一种新的IAsyncEnumerable<T>接口,在对集合进行迭代时,支持异步操作。比如在读取文本中的多行字符串时,如果读取每行字符串的时候使用同步方法,那么会导致线程堵塞。IAsyncEnumerable<T>可以解决这种情况,在迭代的时候支持使用异步方法。也就是说,之前我们使用foreach来对IEnumerable进行迭代,现在可以使用await foreach来对IAsyncEnumerable<T>进行迭代,每个项都是可等待的。这种新的接口称为async-streams,将会随.NET Core 3发布。我们来看一下如何在LINQ中实现异步的迭代。
使用常规的IEnumerable<T>
首先我们创建一个新的Console项目,基于.NET Core 3:
namespace AsyncLinqDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Input the file path:");
var file = Console.ReadLine();
var lines = ReadAllLines(file);
foreach (var line in lines)
{
Console.WriteLine(line);
}
} static IEnumerable<string> ReadAllLines(string file)
{
using (var fs = File.OpenRead(file))
{
using (var sr = new StreamReader(fs))
{
while (true)
{
string line = sr.ReadLine();
if(line == null)
{
break;
}
yield return line;
}
}
}
}
}
}
这是一个很简单的Console程序,实现了一个简单的返回类型为IEnumerable<string>的ReadAllLines(string file)方法,从文本文件中逐行读取文本,并逐行输出。如果文本内容较少的话,没什么问题。但如果我们使用过aync/await,就会了解,在IO操作如读取或写入文件的时候,最好使用异步方法以避免线程阻塞。让我们来改进一下。
使用异步的IAsyncEnumerable<T>
可以优化的是下面这句:
string line = sr.ReadLine();
对于IO操作,最好使用异步方式。这里可使用相应的异步方法:
string line = await sr.ReadLineAsync();
我们说“异步是传染的”,如果这里使用异步,那么相应的该方法的返回值也要使用异步,所以需要将返回值改为static async Task<IEnumerable<string>>,但这样会得到一个错误:
ErrorCS1624The body of 'Program.ReadAllLines(string)' cannot be an iterator block because 'Task<IEnumerable<string>>' is not an iterator interface typeAsyncLinqDemoC:\Source\Workspaces\Console\AsyncLinqDemo\AsyncLinqDemo\Program.cs23Active
因为Task<IEnumerable<string>>并不是一个可以迭代的接口类型,所以我们无法在方法内部使用yield关键字。解决问题的办法是使用新的IAsyncEnumerable接口:
static async IAsyncEnumerable<string> ReadAllLines(string file)
{
using (var fs = File.OpenRead(file))
{
using (var sr = new StreamReader(fs))
{
while (true)
{
string line = await sr.ReadLineAsync();
if(line == null)
{
break;
}
yield return line;
} }
}
}
按F12查看该接口的定义:
namespace System.Collections.Generic
{
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationTokencancellationToken = default);
}
}
这是一个异步的迭代器,并提供了CancellationToken。再按F12查看IAsyncEnumerator<T>的定义,可发现里面是这样的:
namespace System.Collections.Generic
{
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
T Current { get; }
ValueTask<bool> MoveNextAsync();
}
}
这里MoveNextAsync()方法实际是返回了一个结果类型为bool的Task,每次迭代都是可等待的,从而实现了迭代器的异步。
使用await foreach消费IAsyncEnumerable<T>
当我们做了以上改动之后,ReadAllLines()方法返回的是一个支持异步的IAsyncEnumerable,那么在使用的时候,也不能简单的使用foreach了。修改Main方法如下:
static async Task Main(string[] args)
{
Console.WriteLine("Input the file path:");
var file = Console.ReadLine();
var lines = ReadAllLines(file);
await foreach (var line in lines)
{
Console.WriteLine(line);
}
}
首先在foreach之前添加await关键字,还要需要将Main方法由void改为async Task。这样整个程序都是异步执行了,不会再导致堵塞了。这个例子只是一个简单的demo,是否使用异步并不会感觉到明显的区别。如果在迭代内部需要比较重的操作,如从网络获取大量数据或读取大量磁盘文件,异步的优势还是会比较明显的。
使用LINQ消费IAsyncEnumerable<T>
使用LINQ来操作集合是常用的功能。如果使用IEnumberable,在Main方法中可以做如下改动:
var lines = ReadAllLines(file);
var res = from line in lines where line.StartsWith("ERROR: ") selectline.Substring("ERROR: ".Length);
foreach (var line in res)
{
Console.WriteLine(line);
}
或:
var res = lines.Where(x => x.StartsWith("ERROR: ")).Select(x => x.Substring("ERROR: ".Length));
如果使用了新的IAsyncEnumerable,你会发现无法使用Where等操作符了:
ErrorCS1936Could not find an implementation of the query pattern for source type 'IAsyncEnumerable<string>'. 'Where' not found.AsyncLinqDemoC:\Source\Workspaces\Console\AsyncLinqDemo\AsyncLinqDemo\Program.cs16Active
目前LINQ还没有提供对IAsyncEnumerable的原生支持,不过微软提供了一个Nuget包来实现此功能。在项目中打开Nuget Package Manger搜索安装System.Linq.Async,注意该包目前还是预览版,所以要勾选include prerelease才能看到。安装该Nuget包后,Linq查询语句中的错误就消失了。
在System.Linq.Async这个包中,对每个同步的LINQ方法都做了相应的扩展。所以基本上代码无需什么改动即可正常编译。
对于LINQ中的条件语句,也可以使用WhereAwait()方法来支持await:
public static IAsyncEnumerable<TSource> WhereAwait<TSource>(thisIAsyncEnumerable<TSource> source, Func<TSource, int, ValueTask<bool>>predicate);
如需要在条件语句中进行IO或网络请求等异步操作,可以这样用:
var res = lines.WhereAwait(async x => await DoSomeHeavyOperationsAsync(x));
DoSomeHeavyOperationsAsync方法的签名如下:
private static ValueTask<bool> DoSomeHeavyOperationsAsync(string x)
{
//Do some works...
}
小结
通过以上的示例,我们简要了解了如何使用IAsyncEnumerable接口以及如何在LINQ中实现异步查询。在使用该接口时,我们需要创建一个自定义方法返回IAsyncEnumerable<T>来代替IEnumberable<T>,这个方法可称为async-iterator方法,需要注意以下几点:
该方法应该被声明为
async。返回
IAsyncEnumerable<T>。同时使用
await及yield。如await foreach,yield return或yield break等。
例如:
async IAsyncEnumerable<int> GetValuesFromServer()
{
while (true)
{
IEnumerable<int> batch = await GetNextBatch();
if (batch == null) yield break; foreach (int item in batch)
{
yield return item;
}
}
}
此外还有一些限制:
无法在
try的finally块中使用任何形式的yield语句。无法在包含任何
catch语句的try语句中使用yield return语句。
期待.NET Core 3的正式发布!
了解新西兰IT行业真实码农生活
请长按上方二维码关注“程序员在新西兰”
C#8.0: 在 LINQ 中支持异步的 IAsyncEnumerable的更多相关文章
- 在DirectShow中支持DXVA 2.0(Supporting DXVA 2.0 in DirectShow)
这几天在做dxva2硬件加速,找不到什么资料,翻译了一下微软的两篇相关文档.并准备记录一下用ffmpeg实现dxva2,将在第三篇写到.这是第二篇.,英文原址:https://msdn.microso ...
- 在 .NET Core 3.0 中支持 Newtonsoft.Json 的使用
.NET Core 3.0 已经使用了一整套内置的 Josn 序列化/反序列化方案,而且看上去效率还不错.但对于某些项目必须使用到 Newtonsoft.Json 的时候,就会抛出如下异常: Syst ...
- ARM微处理器中支持字节、半字、字三种数据类型,地址的低两位为0是啥意思?
问题: ARM微处理器中支持字节.半字.字三种数据类型,其中,字需要4字节对齐(地址的低两位为0).半字需要2字节对齐(地址的最低位为0).我想问的是括号中的内容是什么意思呢?请牛人帮忙解释一下!谢谢 ...
- Entity Framework 6 Recipes 2nd Edition(11-9)译 -> 在LINQ中使用规范函数
11-9. 在LINQ中使用规范函数 问题 想在一个LINQ查询中使用规范函数 解决方案 假设我们已经有一个影片租赁(MovieRental )实体,它保存某个影片什么时候租出及还回来,以及滞纳金等, ...
- Entity Framework 6 Recipes 2nd Edition(11-11)译 -> 在LINQ中调用数据库函数
11-11. 在LINQ中调用数据库函数 问题 相要在一个LINQ 查询中调用数据库函数. 解决方案 假设有一个任命(Appointment )实体模型,如Figure 11-11.所示, 我们想要查 ...
- Linq中关键字的作用及用法
Linq中关键字的作用及用法 1.All:确定序列中的所有元素是否都满足条件.如果源序列中的每个元素都通过指定谓词中的测试,或者序列为空,则为 true:否则为 false. Demo: 此示例使用 ...
- 【译】.NET Core 3.0 Preview 3中关于ASP.NET Core的更新内容
.NET Core 3.0 Preview 3已经推出,它包含了一系列关于ASP.NET Core的新的更新. 下面是该预览版的更新列表: Razor组件改进: 单项目模板 新的Razer扩展 E ...
- MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法
MVC+Spring.NET+NHibernate .NET SSH框架整合 在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...
- Win10 UWP 开发系列:支持异步的SQLite
上篇文章已经实现了在UWP中使用SQLite作为本地存储,作为移动端的程序,及时响应用户的操作是提高用户体验的重要途径,因此UWP的很多api都是异步的.那么如何使SQLite支持异步呢? 参考SQL ...
随机推荐
- 就服务器项目部署debug谈谈自己的感受
前言 学校小组Project那些外国人啥也不会, 基本上我一个人全包了前端和后端, 说实话这些天来也感受到了写一个比较拿得出手的web确实也不是这么容易的, 特别是我没什么项目经验, 很多时候碰到问题 ...
- WPF绑定到linq表达式
using ClassLibrary;using System;using System.Collections.Generic;using System.Collections.ObjectMode ...
- OpenGL红宝书附带源码编译问题集锦
以下所有源码均在win7,VS2008环境下测试.下不再赘述. 1.所有的.c扩展名请改为.cpp扩展名,以避免不可预测的错误. 想知道会出现什么不可预测的错误..请见我上一篇Blog... 2.如果 ...
- 备份一个个人用的WPF万能转换器
public class CommonCoverter : IValueConverter { /// 转换器参数语法: key1,value1 key2,value2 ... [other,defu ...
- GIS基础软件及操作(一)
原文 GIS基础软件及操作(一) 练习一.浏览地理数据 使用 ArcGIS浏览地理数据 第1步 启动 ArcMap 启动ArcMap.执行菜单命令:开始>>所有程序>> Ar ...
- 微信小程序把玩(三十四)Audio API
原文:微信小程序把玩(三十四)Audio API 没啥可值得太注意的地方 重要属性: 1. wx.getBackgroundAudioPlayerState(object) 获取播放状态 2.wx.p ...
- 零元学Expression Blend 4 - Chapter 27 PathlistBox被Selected时的蓝底蓝框问题
原文:零元学Expression Blend 4 - Chapter 27 PathlistBox被Selected时的蓝底蓝框问题 最近收到网友Cloud的来信,询问我有关放进PathlistBox ...
- WPF使用NAudio录音
代码: using NAudio.Wave; using System.Windows; namespace NAudioDemo { /// <summary> /// MainWind ...
- Jquery 插件开发公开属性顺序的影响.
如下代码拷贝能正常运行. (function ($) { $.fn.DemoPlugin = function (options) { var opts; opts = $.extend({}, $. ...
- Tensorflow数据读取机制
展示如何将数据输入到计算图中 Dataset可以看作是相同类型"元素"的有序列表,在实际使用时,单个元素可以是向量.字符串.图片甚至是tuple或dict. 数据集对象实例化: d ...