Почему использование IAsyncEnumerable медленнее, чем возврат async/await Task <T>?
В настоящее время я тестирую асинхронные потоки C# 8, и кажется, что, когда я пытаюсь запустить приложение, используя старый шаблон использования async/await и возврата Task>, это кажется быстрее. (Я измерил его с помощью секундомера и попробовал запустить его несколько раз, и в результате старый шаблон, о котором я упоминал, кажется несколько быстрее, чем использование IAsyncEnumerable).
Вот простое консольное приложение, которое я написал (я также думаю, что, возможно, я загружаю данные из базы данных неправильно)
class Program
{
static async Task Main(string[] args)
{
// Using the old pattern
//Stopwatch stopwatch = Stopwatch.StartNew();
//foreach (var person in await LoadDataAsync())
//{
// Console.WriteLine($"Id: {person.Id}, Name: {person.Name}");
//}
//stopwatch.Stop();
//Console.WriteLine(stopwatch.ElapsedMilliseconds);
Stopwatch stopwatch = Stopwatch.StartNew();
await foreach (var person in LoadDataAsyncStream())
{
Console.WriteLine($"Id: {person.Id}, Name: {person.Name}");
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
static async Task<IEnumerable<Person>> LoadDataAsync()
{
string connectionString = "Server=localhost; Database=AsyncStreams; Trusted_Connection = True;";
var people = new List<Person>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
//SqlDataReader
await connection.OpenAsync();
string sql = "Select * From Person";
SqlCommand command = new SqlCommand(sql, connection);
using (SqlDataReader dataReader = await command.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
Person person = new Person();
person.Id = Convert.ToInt32(dataReader[nameof(Person.Id)]);
person.Name = Convert.ToString(dataReader[nameof(Person.Name)]);
person.Address = Convert.ToString(dataReader[nameof(Person.Address)]);
person.Occupation = Convert.ToString(dataReader[nameof(Person.Occupation)]);
person.Birthday = Convert.ToDateTime(dataReader[nameof(Person.Birthday)]);
person.FavoriteColor = Convert.ToString(dataReader[nameof(Person.FavoriteColor)]);
person.Quote = Convert.ToString(dataReader[nameof(Person.Quote)]);
person.Message = Convert.ToString(dataReader[nameof(Person.Message)]);
people.Add(person);
}
}
await connection.CloseAsync();
}
return people;
}
static async IAsyncEnumerable<Person> LoadDataAsyncStream()
{
string connectionString = "Server=localhost; Database=AsyncStreams; Trusted_Connection = True;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
//SqlDataReader
await connection.OpenAsync();
string sql = "Select * From Person";
SqlCommand command = new SqlCommand(sql, connection);
using (SqlDataReader dataReader = await command.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
Person person = new Person();
person.Id = Convert.ToInt32(dataReader[nameof(Person.Id)]);
person.Name = Convert.ToString(dataReader[nameof(Person.Name)]);
person.Address = Convert.ToString(dataReader[nameof(Person.Address)]);
person.Occupation = Convert.ToString(dataReader[nameof(Person.Occupation)]);
person.Birthday = Convert.ToDateTime(dataReader[nameof(Person.Birthday)]);
person.FavoriteColor = Convert.ToString(dataReader[nameof(Person.FavoriteColor)]);
person.Quote = Convert.ToString(dataReader[nameof(Person.Quote)]);
person.Message = Convert.ToString(dataReader[nameof(Person.Message)]);
yield return person;
}
}
await connection.CloseAsync();
}
}
Я хотел бы знать, подходит ли IAsyncEnumerable для такого сценария или что-то не так с тем, как я запрашивал данные при использовании IAsyncEnumerable? Возможно, я ошибаюсь, но на самом деле ожидаю, что использование IAsyncEnumerable будет быстрее. (кстати... разница обычно в сотнях миллисекунд)
Я попробовал приложение с образцом данных из 10000 строк.
Вот еще код для заполнения данных на всякий случай...
static async Task InsertDataAsync()
{
string connectionString = "Server=localhost; Database=AsyncStreams; Trusted_Connection = True;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
string sql = $"Insert Into Person (Name, Address, Birthday, Occupation, FavoriteColor, Quote, Message) Values";
for (int i = 0; i < 1000; i++)
{
sql += $"('{"Randel Ramirez " + i}', '{"Address " + i}', '{new DateTime(1989, 4, 26)}', '{"Software Engineer " + i}', '{"Red " + i}', '{"Quote " + i}', '{"Message " + i}'),";
}
using (SqlCommand command = new SqlCommand(sql.Remove(sql.Length - 1), connection))
{
command.CommandType = CommandType.Text;
await connection.OpenAsync();
await command.ExecuteNonQueryAsync();
await connection.CloseAsync();
}
}
}
2 ответа
IAsyncEnumerable<T>
не по наследству быстрее или медленнее, чем Task<T>
. Это зависит от реализации.
IAsyncEnumerable<T>
касается асинхронного извлечения данных, предоставляющих отдельные значения как можно скорее.
IAsyncEnumerable<T>
позволяет производить пакетные значения, которые будут вызывать некоторые из вызовов MoveNextAsync
синхронно, как в следующем примере:
async Task Main()
{
var hasValue = false;
var asyncEnumerator = GetValuesAsync().GetAsyncEnumerator();
do
{
var task = asyncEnumerator.MoveNextAsync();
Console.WriteLine($"Completed synchronously: {task.IsCompleted}");
hasValue = await task;
if (hasValue)
{
Console.WriteLine($"Value={asyncEnumerator.Current}");
}
}
while (hasValue);
await asyncEnumerator.DisposeAsync();
}
async IAsyncEnumerable<int> GetValuesAsync()
{
foreach (var batch in GetValuesBatch())
{
await Task.Delay(1000);
foreach (var value in batch)
{
yield return value;
}
}
}
IEnumerable<IEnumerable<int>> GetValuesBatch()
{
yield return Enumerable.Range(0, 3);
yield return Enumerable.Range(3, 3);
yield return Enumerable.Range(6, 3);
}
Выход:
Completed synchronously: False
Value=0
Completed synchronously: True
Value=1
Completed synchronously: True
Value=2
Completed synchronously: False
Value=3
Completed synchronously: True
Value=4
Completed synchronously: True
Value=5
Completed synchronously: False
Value=6
Completed synchronously: True
Value=7
Completed synchronously: True
Value=8
Completed synchronously: True
Я думаю, что ответ на вопрос «Я хотел бы знать, подходит ли IAsyncEnumerable лучше всего для такого сценария» немного потерялся в примере пакетной обработки @Bizhan и в последующем обсуждении, но повторимся из этого сообщения:
IAsyncEnumerable<T> предназначен для асинхронного извлечения данных, предоставляющих отдельные значения как можно скорее.
OP измеряет общее время чтения всех записей и игнорирует скорость получения первой записи и ее готовности к использованию вызывающим кодом.
Если «такой сценарий» означает считывание всех данных в память как можно быстрее, то IAsyncEnumerable для этого не подходит.
Если важно начать обработку исходных записей до того, как дождаться чтения всех записей, это то, для чего лучше всего подходит IAsyncEnumerable.
Однако в реальном мире вам действительно следует тестировать производительность всей системы, что будет включать фактическую обработку данных, а не простой вывод их на консоль. В частности, в многопоточной системе максимальная производительность может быть достигнута, если начать обработку нескольких записей одновременно как можно быстрее, одновременно считывая больше данных из базы данных. Сравните это с ожиданием, пока один поток прочитает все данные заранее (при условии, что вы можете уместить весь набор данных в память), и только после этого сможете начать его обработку.