我有以下示例代码:
private async Task<IEnumerable<long>> GetValidIds1(long[] ids)
{
var validIds = new List<long>();
var results = await Task.WhenAll(ids.Select(i => CheckValidIdAsync(i)))
.ConfigureAwait(false);
for (int i = 0; i < ids.Length; i++)
{
if (results[i])
{
validIds.Add(ids[i]);
}
}
return validIds;
}
private async Task<IEnumerable<long>> GetValidIds2(long[] ids)
{
var validIds = new ConcurrentBag<long>();
await Task.WhenAll(ids.Select(async i =>
{
var valid = await CheckValidIdAsync(i);
if (valid)
validIds.Add(i);
})).ConfigureAwait(false);
return validIds;
}
private async Task<bool> CheckValidIdAsync(long id);
我目前使用GetValidIds1((,但它的不便之处在于必须在最后使用索引将输入id与结果绑定。
GetValidatIds2((是我想写的,但有一些问题:
- 我在select lambda表达式中有'await'。因为LINQ是惰性评估,我不认为它会阻止其他
CheckValidIdAsync()
调用的启动,但它到底挂起了谁的上下文?根据MSDN文档
等待运算符挂起封闭异步方法的求值,直到其操作数表示的异步操作完成。
所以在这种情况下,封闭的异步方法本身就是lambda表达式,所以它不会影响其他调用?
- 是否有更好的方法来处理异步方法的结果并将该过程的输出收集到列表中
另一种方法是将每个long
ID投影到一个Task<ValueTuple<long, bool>>
,而不是投影到Task<bool>
。通过这种方式,您将能够使用纯LINQ:过滤结果
private async Task<long[]> GetValidIds3(long[] ids)
{
IEnumerable<Task<(long Id, bool IsValid)>> tasks = ids
.Select(async id =>
{
bool isValid = await CheckValidIdAsync(id).ConfigureAwait(false);
return (id, isValid);
});
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results
.Where(e => e.IsValid)
.Select(e => e.Id)
.ToArray();
}
上述GetValidIds3
与您问题中的GetValidIds1
等效。它以与原始ids
相同的顺序返回过滤后的ID。相反,GetValidIds2
不能保证任何订单。如果必须使用并发集合,最好使用ConcurrentQueue<T>
而不是ConcurrentBag<T>
,因为前者保留了插入顺序。即使顺序不重要,保留它也会使调试更容易。