Select 到异步函数的更好方法?
Better way to Select to an async Func?
我一直在重构我的项目中的一个通用模式,发现它并不像使用 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
的异步方法清楚地告诉它类型应该是什么时编译器无法推断类型?
明确地说,我确实需要将 CancellationToken
传递给 Func
,因为它是与外部令牌不同的令牌(具体来说,它是一个链接令牌,绑定外部令牌一个到另一个内部的)。
问题出在select的return中,因为编译器不清楚return的类型是什么。所以,你需要明确return的类型,这里有两种方式:
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 只是匿名方法的 shorthand,而不是类型。因此,如果 return 是基本委托或其他委托类型,如 Action、Func 等,则需要明确指出方法的 return 类型。查看 this 其他answer,根据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()
你真的需要 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 Select
方法相同的参数,但 returns 任务工厂而不是物化任务。
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<Func<CancellationToken, Task<ICollection<Group>>>>
。
我一直在重构我的项目中的一个通用模式,发现它并不像使用 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
的异步方法清楚地告诉它类型应该是什么时编译器无法推断类型?
明确地说,我确实需要将 CancellationToken
传递给 Func
,因为它是与外部令牌不同的令牌(具体来说,它是一个链接令牌,绑定外部令牌一个到另一个内部的)。
问题出在select的return中,因为编译器不清楚return的类型是什么。所以,你需要明确return的类型,这里有两种方式:
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 只是匿名方法的 shorthand,而不是类型。因此,如果 return 是基本委托或其他委托类型,如 Action、Func 等,则需要明确指出方法的 return 类型。查看 this 其他answer,根据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()
你真的需要 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 Select
方法相同的参数,但 returns 任务工厂而不是物化任务。
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<Func<CancellationToken, Task<ICollection<Group>>>>
。