Почему эти задачи выполняются дважды? - PullRequest
0 голосов
/ 12 июля 2020

Я пытаюсь асинхронно добавить несколько объектов в свою базу данных в качестве «упорядочивающей» части моего интеграционного теста.

Однако я получаю двойные записи с исходным кодом:

private async Task<Foo[]> GenerateFoos(params string[] names)
{
    var tasks = names.Select(async name =>
    {
        var foo = new Foo() { Name = name };
        await AddAsync(foo);
        return foo;
    });

    await Task.WhenAll(tasks);

    return tasks.Select(task => task.Result).ToArray();
}

[Test]
public async Task MyTest()
{
    var expected = await GenerateFoos("A", "B", "C");
}

Он генерирует вдвое больше объектов (по два каждого) в моей базе данных, и я не могу понять почему.

Я просмотрел другие примеры в Интернете, но все они предполагают, что Метод asyn c (AddAsync в моем случае) возвращает объекты, которые я хочу вернуть, но здесь это не так; as AddAsync возвращает только идентификатор, но я хочу вернуть сам добавленный объект.

Я дважды переработал код, и эти две альтернативы не вставляют дубликаты:

// Alternative 1
    
var result = new List<Corporation>();
        
foreach (var name in names)
{
    var foo= new Foo() { Name = name };
    await AddAsync(foo);
    result.Add(foo);
}

return result.ToArray();

// Alternative 2

var foos = names.Select(name => new Foo{ Name = name });

await Task.WhenAll(foos.Select(foo => AddAsync(foo)));

return foos.ToArray();

Так что я почти уверен, что ошибка связана с моим кодом, а не с кодом AddAsync. Но я не понимаю, почему первый ведет себя иначе. Хотя я не ожидаю, что содержимое AddAsync имеет здесь значение, я добавляю его для завершения:

public static async Task AddAsync<TEntity>(TEntity entity)
    where TEntity : class
{
    using var scope = _scopeFactory.CreateScope();

    var context = scope.ServiceProvider.GetService<MyDbContext>();

    context.Add(entity);

    await context.SaveChangesAsync();
}

Может ли кто-нибудь определить, почему мои задачи выполняются дважды?

1 Ответ

6 голосов
/ 13 июля 2020

var tasks содержит запрос, не результат запроса.

Этот запрос перечисляется дважды, на Task.WhenAll() и tasks.Select(). Поскольку вызов AddAsync находится внутри функции, созданной во время перечисления, он выполняется дважды.

Материализация запроса решает проблему:

var tasks = names.Select(async name =>
    {
        var foo = new Foo() { Name = name };
        await AddAsync(foo);
        return foo;
    })
    .ToList();

Обратите внимание, что ваша Альтернатива 2 содержит та же проблема с двойным перечислением, но на этот раз перечисление вызывает только ненужное создание новых экземпляров Foo. Вызовы AddAsync не входят в перечисление.

...