简体   繁体   English

为什么使用 IAsyncEnumerable 比返回 async/await 任务慢<T> ?

[英]Why is using IAsyncEnumerable slower than returning async/await Task<T>?

I'm currently testing out C# 8's async streams, and it seems that when I try to run the application using the old pattern of of using async/await and returning Task> it seems to be faster.我目前正在测试 C# 8 的异步流,似乎当我尝试使用使用 async/await 并返回 Task> 的旧模式运行应用程序时,它似乎更快。 (I measured it using a stopwatch and tried running it multiple times, and the result was that the old pattern I mentioned seems somewhat faster than using IAsyncEnumerable). (我使用秒表对其进行测量并尝试多次运行,结果是我提到的旧模式似乎比使用 IAsyncEnumerable 快一些)。

Here's a simple Console App that I wrote (I'm also thinking perhaps I'm loading the data from database the wrong way)这是我写的一个简单的控制台应用程序(我也在想我可能以错误的方式从数据库加载数据)

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

I would like to know whether IAsyncEnumerable is not best suited for this kind of scenario or there was something wrong with how I queried the data while using IAsyncEnumerable?我想知道 IAsyncEnumerable 是不是最适合这种场景,还是我在使用 IAsyncEnumerable 时查询数据的方式有问题? I might be wrong but I actually expect using IAsyncEnumerable would be faster.我可能是错的,但我实际上希望使用 IAsyncEnumerable 会更快。 (by the way...the difference are usually in hundreds of milliseconds) (顺便说一句......差异通常在数百毫秒内)

I tried the application with a sample data of 10,000 rows.我使用 10,000 行的示例数据尝试了该应用程序。

Here's also the code for populating the data just in case...这也是用于填充数据的代码,以防万一......

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

            }
        }

IAsyncEnumerable<T> is not inherently faster or slower than Task<T> . IAsyncEnumerable<T>本质上并不比Task<T>快或慢。 It depends on the implementation.这取决于实现。

IAsyncEnumerable<T> is about asynchronously retrieving data providing individual values as soon as possible. IAsyncEnumerable<T>是关于尽快异步检索提供单个值的数据。

IAsyncEnumerable<T> allows batch producing values which will make some of the invocations of MoveNextAsync synchronous, as in the next example: IAsyncEnumerable<T>允许批量生成值,这将使MoveNextAsync一些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);
}

Output:输出:

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

I think the answer to the question of "I would like to know whether IAsyncEnumerable is not best suited for this kind of scenario" got a bit lost in @Bizhan's example of batching and the ensuing discussion, but to reiterate from that post:我认为“我想知道 IAsyncEnumerable 是否最适合这种情况”这个问题的答案在@Bizhan 的批处理示例和随后的讨论中有点迷失,但要重申该帖子:

IAsyncEnumerable<T> is about asynchronously retrieving data providing individual values as soon as possible . IAsyncEnumerable<T> 是关于异步检索数据,尽快提供单个值

The OP is measuring the total time to read all records and ignoring how quickly the first record is retrieved and ready to be used by the calling code. OP 正在测量读取所有记录的总时间,并忽略检索第一条记录并准备好供调用代码使用的速度。

If "this kind of scenario" means reading all the data into memory as fast as possible, then IAsyncEnumerable is not best suited for that.如果“这种场景”意味着尽可能快地将所有数据读入内存,那么 IAsyncEnumerable 并不适合这种情况。

If it is important to start processing the initial records before waiting for all of the records to be read, that is what IAsyncEnumerable is best suited for.如果在等待读取所有记录之前开始处理初始记录很重要,那么 IAsyncEnumerable 最适合。

However, in the real world, you should really be testing the performance of the total system, which would include actual processing of the data as opposed to simply outputting it to a console.然而,在现实世界中,您确实应该测试整个系统的性能,这将包括数据的实际处理,而不是简单地将其输出到控制台。 Particularly in a multithreaded system, maximum performance could be gained by starting to process multiple records simultaneously as quickly as possible, while at the same time reading in more data from the database.特别是在多线程系统中,通过尽可能快地开始同时处理多个记录,同时从数据库中读取更多数据,可以获得最大性能。 Compare that to waiting for a single thread to read all of the data up front (assuming you could fit the entire dataset into memory) and only then being able to start processing it.相比之下,等待单个线程预先读取所有数据(假设您可以将整个数据集放入内存中),然后才能够开始处理它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM