【问题标题】:Better way to Select to an async Func?选择异步函数的更好方法?
【发布时间】:2020-08-18 10:53:35
【问题描述】:

我一直在重构我的项目中的一个常见模式,发现它并不像使用 LINQ Select 到异步函数那么简单。

对于上下文,这是当前的完成方式。

async Task<ICollection<Group>> ExecuteQueryGroupsForDomain(DomainInfo domain, int batchSize, CancellationToken ct)
{
    try
    {
        return await BlahBlahActuallyGoGetGroupsForDomainHere(domain, batchSize, ct);
    }
    catch (Exception e)
    {
        return null;
    }
}

var executeQueries = new List<Func<CancellationToken, Task<ICollection<Group>>>>();

domains.ForEach(domain =>
    executeQueries.Add(async (ct) =>
        await ExecuteQueryGroupsForDomain(domain, 123, ct)));


现在,如果我尝试使用 LINQ 替换 ForEach 循环部分:

var executeQueries = domains.Select(domain =>
    async (ct) => await ExecuteQueryGroupsForDomain(domain, 123, ct));

它抱怨Type arguments cannot be inferred by the usage 这让我相信我没有从Select 返回任何东西,但我显然正在返回我想要的Func

有没有更好的方法来创建Func 的列表,最好避免显式转换?还有什么解释为什么当Select'd async 方法清楚地告诉它应该是什么类型时编译器无法推断类型?


需要明确的是,我确实需要将CancellationToken 传递给Func,因为它与外部令牌不同(具体而言,它是将外部令牌与另一个内部令牌联系起来的链接令牌) .

【问题讨论】:

  • 您真的要同时启动所有这些异步任务吗?
  • @JeremyLakeman 是的,每个任务都将通过网络相互独立地获取一些数据,服务器可以处理的远远超过我将简要介绍的并发负载。
  • @pinkfloydx33 试过了,同样的问题。我也尝试过没有任何取消令牌的外观,并且没有看到任何变化,因此令牌在这里没有影响。
  • domains.Select(async domain =&gt; await Execute(...)) 应该可以工作,但您会丢失 CT 参数。但是请看下面的答案。这有点小,但他是对的。无法推断类型,您需要明确
  • Also is there any explanation why the compiler is unable to infer the type when the Select'd async method is clearly telling it what the type should be? - FuncAction 不是唯一的委托类型。可能有任意数量的委托类型与该签名匹配,这就是编译器不知道您想要哪一种的原因。

标签: c# .net linq select func


【解决方案1】:

您可以通过使用如下所示的扩展方法获得一些可读性。它采用与 LINQ Select 方法相同的参数,但返回任务工厂而不是具体化任务。

public static IEnumerable<Func<CancellationToken, Task<TResult>>> SelectTaskFactory
    <TSource, TResult>(this IEnumerable<TSource> source,
    Func<TSource, CancellationToken, Task<TResult>> selector)
{
    return source.Select(item =>
    {
        return new Func<CancellationToken, Task<TResult>>(ct => selector(item, ct));
    });
}

使用示例:

var executeQueries = domains.SelectTaskFactory(async (domain, ct) =>
{
    return await ExecuteQueryGroupsForDomain(domain, 123, ct);
}).ToList();

executeQueries 变量的类型是List&lt;Func&lt;CancellationToken, Task&lt;ICollection&lt;Group&gt;&gt;&gt;&gt;

【讨论】:

    【解决方案2】:

    问题在于select的返回,因为编译器并不清楚返回的类型是什么。所以,你需要明确返回的类型,这里有两种方式:

    executeQueries = domains.Select(domain => 
        new Func<CancellationToken, Task<ICollection<Group>>>(token => 
            this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList();
    
    executeQueries = domains
        .Select<DomainInfo, Func<CancellationToken, Task<ICollection<Group>>>>(domain =>
            ct => this.ExecuteQueryGroupsForDomain(domain, 123, ct)).ToList();
    

    ================================================ ========================

    编辑 1: 编译器无法从 lambda 表达式推断类型,因为 lambda 只是匿名方法的简写,而不是类型。因此,您需要明确并指出方法的返回类型,如果返回是基委托或其他委托类型,如 Action、Func 等。请查看 this 其他答案,其中根据错误编译器解释C# 4 规范。

    如果您需要将原始代码转换为更具可读性的内容,我认为没有其他方式更具可读性。以下是可以编写代码的其他方式:

    foreach (var domain in domains) {
        executeQueries.Add(token => this.ExecuteQueryGroupsForDomain(domain, 123, token));
    }
    executeQueries.AddRange(domains
        .Select(domain => (Func<CancellationToken, Task<ICollection<Group>>>) (token => 
            this.ExecuteQueryGroupsForDomain(domain, 123, token))));
    executeQueries =
        (from domain in domains
        select new Func<CancellationToken, Task<ICollection<Group>>>(token => 
            this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList()
    

    【讨论】:

    • 好的,谢谢。不知道为什么编译器不能推断类型,该方法清楚地在那里返回任何解释?我希望有一些更具可读性的东西,我认为这比我写的ForEach 循环更糟糕。有没有其他方法可以推断类型,或者是预先知道类型的静态方法或其他什么?
    【解决方案3】:

    你真的需要Func的吗?

    如果实际的CancellationToken 已经存在,您可以使用以下内容。

    // create and start a Task for each domain
    var executeQueryTasks = domains.Select(domain => ExecuteQueryGroupsForDomain(domain, 123, ct));
    
    // wait until all tasks are finished and get the result in an array
    var executedQueries = await Task.WhenAll(executeQueryTasks);
    

    【讨论】:

    • 我确实需要Func。存在的取消令牌是一个外部令牌,而我传递给Func 的是一个链接令牌,它将外部令牌与单独的内部令牌联系起来。
    猜你喜欢
    • 2019-12-17
    • 2020-03-30
    • 2011-01-05
    • 1970-01-01
    • 2019-08-01
    • 2013-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多