C# 选择异步函数的更好方法?

C# 选择异步函数的更好方法?,c#,.net,linq,select,func,C#,.net,Linq,Select,Func,我一直在重构我的项目中的一个公共模式,发现它不像使用LINQ Select来实现异步函数那么简单 对于上下文,下面是当前如何完成的 async Task<ICollection<Group>> ExecuteQueryGroupsForDomain(DomainInfo domain, int batchSize, CancellationToken ct) { try { return await BlahBlahActuallyGoGe

我一直在重构我的项目中的一个公共模式,发现它不像使用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)));

它抱怨类型参数不能由用法推断,这使我相信我没有从Select返回任何东西,但我显然返回了我想要的Func

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


为了清楚起见,我确实需要将CancellationToken传递给Func,因为它与外部令牌不同。具体来说,它是一个链接令牌,将外部令牌与另一个内部令牌绑定在一起。

问题在于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等,则需要显式指定方法的返回类型。请查看其他答案,其中解释基于C4规范的错误编译器

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

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()
你真的需要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);

通过使用下面的扩展方法,您可能会获得一些可读性。它接受与LINQ方法相同的参数,但返回任务工厂,而不是具体化的任务

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));
    });
}

executeQueries变量的类型是List。

是否确实要同时启动所有这些异步任务?@JeremyLakeman Yes每个任务都将通过网络相互独立地获取一些数据,并且服务器可以处理的数据远远超过我将在其上短暂施加的并发负载。@pinkfloydx33尝试了这一点,同样的问题。我还尝试了没有任何取消令牌的外观,但没有看到任何更改,因此令牌在这里没有影响。domains.Selectasync domain=>wait Execute。。。应该可以工作,但会丢失CT参数。但请看下面的答案。这有点小,但他是对的。类型无法推断,您需要明确说明。那么,当Select'd async方法明确告诉编译器应该是什么类型时,有没有解释为什么编译器无法推断类型Func和Action不是唯一的委托类型。可以有任意数量的委托类型与该签名匹配,这就是为什么编译器不知道您想要哪一种。我确实需要Func。当前的取消令牌是一个外部令牌,而我传递给Func的是一个链接令牌,它将外部令牌绑定到一个单独的内部令牌。好的,这很有效,谢谢。虽然不确定为什么编译器不能推断类型,但该方法显然会返回任何解释?虽然我希望有更可读的东西,但我认为这比我写的ForEach循环更糟糕。有没有其他方法来推断类型?可能是一个静态方法,它事先知道类型,或者是其他什么?
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();