LINQ。写了几年代码的兄弟都或多或少用过。早期的LINQ主要是同步的。直到C#8.0加入了IAsyncEnumerable,LINQ才真正变异步。这是一个很好的改变。通过System.Linq.Async库提供的扩展,可以在Where、Select和GroupBy等各种地方使用异步。但其实我在review代码的时候,看到很多人的代码,并没有按照异步规则使用,坑了很多。举个简单的例子:staticasyncTask>Where(thisIAsyncEnumerablesource,Funcpredicate){varfilteredItems=newList();awaitforeach(variteminsource){if(predicate)(item)){filteredItems.Add(item);}}returnfilteredItems;}这种写法,看似使用了async/await,实际上并没有实现异步,程序还是同步运行的。换句话说,这只是一个表面上的异步,并没有任何延迟执行的效果。1.延迟执行其实这里正确的写法很简单,使用异步迭代器(如果需要了解异步迭代器可以看我的另一篇推文):staticasyncIAsyncEnumerableWhere(thisIAsyncEnumerablesource,Funcpredicate){awaitforeach(variiteminsource){if(predicate(item)){yieldreturnitem;}}}这样编译器会把方法传递给状态机,异步枚举器在实际调用时通过枚举器返回。看调用过程:IAsyncEnumerableusers=...IAsyncEnumerablefilteredUsers=users.Where(User=>User.Name=="WangPlus");awaitforeach(UseruserinfilteredUsers){Console.WriteLine(user.Age);}在这个调用示例中,实际方法不会立即从Where开始。只有在下面的foreach中,Where方法才真正开始执行。延迟执行,这是异步LINQ的第一个优势。2.Stream执行Stream执行也依赖于异步迭代器。所谓流式执行,其实就是根据调用的要求一次返回一个对象。通过使用异步迭代器,您可以一个一个地返回单个对象,而不是一次返回所有对象,直到枚举完所有对象。流执行需要一个棘手的代码,以及C#8.0的一个新特性:本地方法。看代码:staticIAsyncEnumerableWhere(thisIAsyncEnumerablesource,Funcpredicate){returnCore();asyncIAsyncEnumerableCore(){awaitforeach(variteminsource){if(predicate(项目)){yieldreturnitem;}}}}3。取消异步LINQ的前两节,是关于异步LINQ的执行。通常使用异步LINQ的原因是因为执行时间长,一般需要一段时间才能完成。因此,消除异步LINQ很重要。想象一下一个长DB查询超时的情况,怎么处理呢?为了支持取消,IAsyncEnumerable.GetEnumerator本身接受一个CancellationToken参数来中止任务,并使用扩展方法连接foreach调用:CancellationTokencancellationToken=...IAsyncEnumerableusers=...IAsyncEnumerablefilteredUsers=users。Where(User=>User.Name=="WangPlus");awaitforeach(varUserinfilteredUsers.WithCancellation(cancellationToken)){Console.WriteLine(User.Age);}同时,在上面的Where定义中,还要响应CancellationToken参数:staticIAsyncEnumerableWhere(thisIAsyncEnumerablesource,Funcpredicate){returnCore();asyncIAsyncEnumerableCore([EnumeratorCancellation]CancellationTokencancellationToken=default){awaitforeach(variiteminsource.WithCancellation(cancellationToken)){if(predicate(item)){yieldreturnitem;}}}}更多解释:在Where方法中,CancellationToken只能添加到本地函数Core,一个简单的原因是Where本身不是一个异步方法,我们不想从Where中传递它。想象一下:Users.Where(xxx,cancellationToken).Select(xxx,cancellationToken).OrderBy(xxx,cancellationToken);这样的代码会让人晕倒。因此,我们将通过上述方法让消费者在枚举数据时传递CancellationToken,从而达到取消异步操作的目的。4.处理ConfigureAwait(false)这又是异步必须要注意的部分,其实就是context。通常在大多数方法中,我们不需要关注上下文,但总有一些情况需要在等待的异步操作恢复后返回到某个上下文。在为UI线程编码时通常需要考虑这种情况。这就是很多人提到的异步死锁的原因。处理也很简单:staticIAsyncEnumerableWhere(thisIAsyncEnumerablesource,Funcpredicate){returnCore();asyncIAsyncEnumerableCore([EnumeratorCancellation]CancellationTokencancellationToken=default){awaitforeach(variiteminsource.WithCancellation(cancellationToken).ConfigureAwait(false)){if(predicate(item)){yieldreturnitem;}}}}这里多说几句:根据微软的说法,awaitforeach本身是基于模式的,而WithCancellation和ConfigureAwait返回相同的结构ConfiguredCancelableAsyncEnumerable。此结构不实现IAsyncEnumerable接口,但有一个GetAsyncEnumerator方法,该方法返回一个带有MoveNextAsync、Current和DisposeAsync的枚举器,因此它可以等待foreach。5.方法扩展在上面4节中,我们已经完成了一个Where异步LINQ的全部内容。然而,这种方法有一些局限性和不足。熟悉异步的兄弟应该已经看到了一个delegatepredicate是用来进行数据过滤的,这个delegate就是一个同步方法。其实按照微软对异步LINQ的约定,每个算子都应该通过三种方式重载:同步委托的实现就是上面的Where方法;异步委托的实现指的是异步返回类型的实现,通常这个方法名会加上一个Await作为后缀,例如:WhereAwait;可以接受取消的异步委托的实现,通常这个方法会以AwaitWithCancellation为后缀,例如:WhereAwaitWithCancellation。参考微软的异步方法,基本都是用这种结构来命名方法名。接下来,我们也通过这种方式对Where方法进行了几次重载。正如上面WhereAwait方法中提到的,这将是一个异步实现。因此,条件部分不能使用像这样的Func代码:EnumeratorCancellation]CancellationTokencancellationToken=default){awaitforeach(variiteminsource.WithCancellation(cancellationToken).ConfigureAwait(false)){if(awaitpredicate(item).ConfigureAwait(false)){yieldreturnitem;}}}}是这样调用的:IAsync>Enumerable<用户filteredUsers=users.WhereAwait(asyncuser=>awaitsomeIfFunction());在上面的基础上,增加了取消操作。看代码:staticIAsyncEnumerableWhereAwaitWithCancellation(thisIAsyncEnumerablesource,Func>predicate){returnCore();asyncIAsyncEnumerableCore([EnumeratorCancellation]CancellationTokencancellationToken=default){awaitforeach(variiteminsource.WithCancellation(cancellationToken).ConfigureAwait(false)){if(awaitpredicate(item,cancellationToken).ConfigureAwait(false)){yieldreturnitem;}}}}这样调用:IAsyncEnumerablefilteredUsers=users.WhereAwait(WithCancellation(用户,令牌)=>等待someIfFunction(用户,令牌));6.综上所述,异步LINQ多用于LINQ的扩展方法,而不是我们平时习惯的直接写LINQ。其实异步LINQ的扩展对LINQ本身有比较大的强化作用。无论是性能上还是可读性上,只有用多了才会过瘾。