站长统计是什么意思,县市区没有建设信用网站和平台,深圳网站官网建设,wordpress链接样式设置方法用不好异步 LINQ#xff0c;基本上就等于用不好 LINQ 了。LINQ 这个东西#xff0c;出来很早了#xff0c;写过几年代码的兄弟们#xff0c;或多或少都用过一些。早期的 LINQ#xff0c;主要是同步的#xff0c;直到 C# 8.0 加入 IAsyncEnumerable#xff0c;LINQ 才真正… 用不好异步 LINQ基本上就等于用不好 LINQ 了。LINQ 这个东西出来很早了写过几年代码的兄弟们或多或少都用过一些。早期的 LINQ主要是同步的直到 C# 8.0 加入 IAsyncEnumerableLINQ 才真正转向异步。这本来是个非常好的改变配合 System.Linq.Async 库提供的扩展可以在诸如 Where、Select、GroupBy 等各种地方用到异步。但事实上在我 Review 代码时见了很多人的代码并没有按异步的规则去使用出现了很多的坑。举个简单的例子static async TaskListT WhereT(this IAsyncEnumerableT source, FuncT, bool predicate)
{var filteredItems new ListT();await foreach (var item in source){if (predicate(item)){filteredItems.Add(item);}}return filteredItems;
}这样的写法看着是用到了 async / await 对但实际上并没有实现异步程序依然是按照同步在运行。换句话说这只是一个样子上的异步实际没有任何延迟执行的效果。1. 延迟执行其实这儿正确的写法也挺简单用到的就是个异步的迭代器关于异步迭代器如果需要了解可以看我的另一篇推文static async IAsyncEnumerableT WhereT(this IAsyncEnumerableT source, FuncT, bool predicate)
{await foreach (var item in source){if (predicate(item)){yield return item;}}
}这种写法下编译器会将方法转了状态机并在实际调用时才通过枚举器返回异步枚举项。看看调用过程IAsyncEnumerableUser users ...
IAsyncEnumerableUser filteredUsers users.Where(User User.Name WangPlus);await foreach (User user in filteredUsers)
{Console.WriteLine(user.Age);
}在这个调用的例子中在 Where 时实际方法并不会马上开始。只有在下面 foreach 时才真正开始执行 Where 方法。延迟执行这是异步 LINQ 的第一个优势。2. 流执行流执行依托的也是异步迭代器。所谓流执行其实就是根据调用的要求一次返回一个对象。通过使用异步迭代器可以不用一次返回所有的对象而是一个一个地返回单个的对象直到枚举完所有的对象。流执行需要做个技巧性的代码需要用到一个 C# 8.0 的新特性局部方法。看代码static IAsyncEnumerableT WhereT(this IAsyncEnumerableT source, FuncT, bool predicate)
{return Core();async IAsyncEnumerableT Core(){await foreach (var item in source){if (predicate(item)){yield return item;}}}
}3. 取消异步 LINQ前面两个小节写的是异步 LINQ 的执行。通常使用异步 LINQ 的原因就是因为执行时间长一般需要一段时间来完成。因此取消异步 LINQ 就很重要。想象一下一个长的 DB 查询已经超时了的情况该怎么处理为了支持取消IAsyncEnumerable.GetEnumerator 本身接受一个 CancellationToken 参数来中止任务并用一个扩展方法挂接到 foreach 调用CancellationToken cancellationToken ...
IAsyncEnumerableUser users ...
IAsyncEnumerableUser filteredUsers users.Where(User User.Name WangPlus);await foreach (var User in filteredUsers.WithCancellation(cancellationToken))
{Console.WriteLine(User.Age);
}同时在上面的 Where 定义中也要响应 CancellationToken 参数static IAsyncEnumerableT WhereT(this IAsyncEnumerableT source, FuncT, bool predicate)
{return Core();async IAsyncEnumerableT Core([EnumeratorCancellation] CancellationToken cancellationToken default){await foreach (var item in source.WithCancellation(cancellationToken)){if (predicate(item)){yield return item;}}}
}多解释一下在 Where 方法中CancellationToken 只能加到局部函数 Core 中一个简单的原因是 Where 本身并不是异步方法而且我们也不希望从 Where 往里传递。想象一下Users.Where(xxx, cancellationToken).Select(xxx, cancellationToken).OrderBy(xxx, cancellationToken);这样的代码会让人晕死。所以我们会采用上面的方式允许消费者在枚举数据时传递 CancellationToken 来达到取消异步操作的目的。4. 处理ConfigureAwait(false)这是另一个异步必须要注意的部分其实就是上下文。通常大多数的方法我们不需要关注上下文但总有一些需要在等待的异步操作恢复后需要返回到某个上下文的情况。这种情况在 UI 线程编码时通常都需要考虑。很多人提到的异步死锁就是这个原因。处理也很简单static IAsyncEnumerableT WhereT(this IAsyncEnumerableT source, FuncT, bool predicate)
{return Core();async IAsyncEnumerableT Core([EnumeratorCancellation] CancellationToken cancellationToken default){await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)){if (predicate(item)){yield return item;}}}
}这儿也多说两句按微软的说法await foreach 本身是基于模式的WithCancellation 和 ConfigureAwait 返回同样的结构体 ConfiguredCancelableAsyncEnumerable。这个结构体没有实现 IAsyncEnumerable 接口而是做了一个 GetAsyncEnumerator 方法返回一个具有 MoveNextAsync、Current、DisposeAsync 的枚举器因此可以 await foreach 。5. 方法扩展上面 4 个小节我们完成了一个 Where 异步 LINQ 的全部内容。不过这个方法有一些限制和不足。熟悉异步的兄弟们应该已经看出来了里面用了一个委托 predicate 来做数据过滤而这个委托是个同步的方法。事实上根据微软对异步 LINQ 的约定每个操作符应该是三种重载同步委托的实现就是上面的 Where 方法异步委托的实现这个是指具有异步返回类型的实现通常这种方法名称会用一个 Await 做后缀例如WhereAwait可以接受取消的异步委托的实现通常这种方法会用 AwaitWithCancellation 做后缀例如WhereAwaitWithCancellation。参考微软的异步方法基本上都是以这种结构来命名方法名称的。下面我们也按这个方式来做一个 Where 方法的几个重载。WhereAwait 方法上面说了这会是一个异步实现。所以条件部分就不能用 FuncT, bool 这样的同步委托了而需要改为 FuncT, ValueTaskbool。这里的 ValueTask 倒不是必须用 Task 也可以只不过我更习惯用 ValueTask。两个的区别Task 是类有上下文而 ValueTask 是结构。代码是这样static IAsyncEnumerableT WhereAwaitT(this IAsyncEnumerableT source, FuncT, ValueTaskbool predicate)
{return Core();async IAsyncEnumerableT Core([EnumeratorCancellation] CancellationToken cancellationToken default){await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)){if (await predicate(item).ConfigureAwait(false)){yield return item;}}}
}调用时是这样IAsyncEnumerableUser filteredUsers users.WhereAwait(async user await someIfFunction());WhereAwaitWithCancellation方法在上面的基础上又加了一个取消操作。看代码static IAsyncEnumerableT WhereAwaitWithCancellationT(this IAsyncEnumerableT source, FuncT, CancellationToken, ValueTaskbool predicate)
{return Core();async IAsyncEnumerableT Core([EnumeratorCancellation] CancellationToken cancellationToken default){await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)){if (await predicate(item, cancellationToken).ConfigureAwait(false)){yield return item;}}}
}调用时是这样IAsyncEnumerableUser filteredUsers users.WhereAwaitWithCancellation(async (user, token) await someIfFunction(user, token));6. 总结异步 LINQ多数是在 LINQ 的扩展方法中使用而不是我们通常习惯的 LINQ 直写。事实上异步 LINQ 的扩展对 LINQ 本身是有比较大的强化作用的不管从性能还是可读性上用多了只会更爽。喜欢就来个三连让更多人因你而受益