Лучший способ выбрать асин c Fun c? - PullRequest
5 голосов
/ 04 мая 2020

Я реорганизовал общий шаблон в своем проекте и обнаружил, что это не так просто, как использование LINQ Select для асинхронной c функции.

Для контекста, вот как это делается в настоящее время.

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


Теперь, если я попытаюсь заменить секцию ForEach l oop с помощью LINQ:

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 asyn c четко указывает, каким должен быть тип?


Чтобы было понятно, я нужно передать CancellationToken Func, потому что это токен, отличный от внешнего (в частности, это связанный токен, связывающий внешний с другим внутренним).

Ответы [ 3 ]

2 голосов
/ 04 мая 2020

Проблема в возвратах 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();

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

EDIT 1: компилятор не может вывести тип из лямбда-выражения, потому что лямбда - это просто сокращение для анонимного метода, а не типа. Таким образом, вам нужно быть явно и указывать тип возвращаемого значения метода, если возвращаемое значение - базовый Делегат или другой тип делегата, такой как Action, Fun c, et c. Прочтите этот другой ответ, где объясняется ошибка компилятора на основе C# 4 spe c.

Если вам нужно преобразовать исходный код во что-то более читаемое, я не буду думаю, есть другой способ более читабельным. Вот другие способы написания кода:

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()
1 голос
/ 04 мая 2020

Вы можете улучшить читаемость, используя метод расширения, подобный приведенному ниже. Он принимает те же аргументы, что и метод 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<Func<CancellationToken, Task<ICollection<Group>>>>.

1 голос
/ 04 мая 2020

Вам действительно нужны 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);
...