如何使用异步谓词过滤列表

时间:2017-11-12 22:25:10

标签: c# linq asynchronous

假设您有一个字符串列表(或任何其他类型的字符串,仅使用字符串作为示例),例如

IEnumerable<string> fullList = ...;

和异步谓词,例如

static Task<bool> IncludeString(string s) { ... }

通过该谓词过滤列表的最简单方法是什么,具有以下约束:

  1. 谓词不应该按顺序运行(假设列表很长而且异步谓词很慢)
  2. 生成的过滤列表应保留排序
  3. 我确实找到了一个解决方案,但它涉及创建一个临时列表,其中包含每个条目的谓词结果,然后使用它进行过滤。它感觉不够优雅。这是:

    var includedIndices = await Task.WhenAll(fullList.Select(IncludeString));
    var filteredList = fullList.Where((_, i) => includedIndices[i]);
    

    通过简单的框架调用感觉就像是可能的,但我找不到。

2 个答案:

答案 0 :(得分:1)

您可以创建自己需要的Linq函数的实现,即

public static async Task<IEnumerable<TIn>> FilterAsync<TIn>(this IEnumerable<TIn> source, Func<TIn, Task<bool>> action)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (action == null) throw new ArgumentNullException(nameof(action));

    var result = new List<TIn>();
    foreach (var item in source)
    {
        if (await action(item))
        {
            result.Add(item);
        }
    }

    return result;
}

然后就可以这样使用

IEnumerable<string> example = new List<string> { "a", "", null, "   ", "e" };
var validStrings = await example.FilterAsync(IncludeString);
// returns { "a", "e" }

考虑到IncludeString

的实施
public static Task<bool> IncludeString(string s) {
    return Task.FromResult(!string.IsNullOrWhiteSpace(s));
}

因此它基本上为列表中的每个项目运行async Func<int, Task<bool>>

答案 1 :(得分:1)

它并不是特别优雅,但您可以在select Task.ContinueWith调用中创建匿名类型,等待对该数组的WhenAll调用,并使用包含在其中的值那些任务结果。

public async Task<T[]> FilterAsync<T>(IEnumerable<T> sourceEnumerable, Func<T, Task<bool>> predicateAsync)
{
    return (await Task.WhenAll(
        sourceEnumerable.Select(
            v => predicateAsync(v)
            .ContinueWith(task => new { Predicate = task.Result, Value = v })))
        ).Where(a => a.Predicate).Select(a => a.Value).ToArray();
}

示例用法(用于演示的补充功能):

// Returns { "ab", "abcd" } after 1000ms
string[] evenLengthStrings = await FilterAsync<string>(new string[] { "a", "ab", "abc", "abcd" }, (async s => { await Task.Delay(1000); return s.Length % 2 == 0; }));

请注意,即使没有ToArray调用,返回的枚举也将在枚举时重新枚举可枚举的源 - 它不会是惰性的,因为Task.WhenAll没有&#39 ; t返回一个LINQy懒惰的可枚举。