5

我目前正在测试 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 会更快。(顺便说一句......差异通常以数百毫秒为单位)

我使用 10,000 行的样本数据尝试了该应用程序。

这也是填充数据的代码,以防万一......

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

            }
        }
4

2 回答 2

3

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
于 2020-01-16T19:50:06.480 回答
0

我认为“我想知道 IAsyncEnumerable 是否最适合这种情况”这个问题的答案在@Bizhan 的批处理示例和随后的讨论中有点迷失了,但从那篇文章中重申:

IAsyncEnumerable<T> 是关于尽快异步检索提供单个值的数据。

OP 正在测量读取所有记录的总时间,而忽略了检索第一条记录并准备好供调用代码使用的速度。

如果“这种情况”意味着尽可能快地将所有数据读入内存,那么 IAsyncEnumerable 并不是最适合这种情况。

如果在等待读取所有记录之前开始处理初始记录很重要,那么 IAsyncEnumerable 最适合。

但是,在现实世界中,您应该真正测试整个系统的性能,这将包括数据的实际处理,而不是简单地将其输出到控制台。特别是在多线程系统中,通过尽可能快地开始同时处理多条记录,同时从数据库中读取更多数据,可以获得最大性能。将其与等待单个线程预先读取所有数据(假设您可以将整个数据集放入内存)然后才能开始处理它进行比较。

于 2021-06-11T19:53:07.737 回答