MST

星途 面试题库

面试题:C#异步迭代器与yield return在复杂业务场景下的应用

假设你正在开发一个电商系统,需要从多个不同的数据源(如数据库、外部API)异步获取商品信息并以迭代的方式返回给上层调用。请设计一个使用C#异步迭代器和yield return语句的方案来实现这个功能,并说明如何处理可能出现的并发问题和数据一致性问题。
43.5万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试
  1. 使用C#异步迭代器和yield return实现异步获取商品信息

    using System;
    using System.Collections.Generic;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    public class ProductInfo
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
    
    public class ProductFetcher
    {
        private readonly string _connectionString;
        private readonly HttpClient _httpClient;
    
        public ProductFetcher(string connectionString)
        {
            _connectionString = connectionString;
            _httpClient = new HttpClient();
        }
    
        public async IAsyncEnumerable<ProductInfo> FetchProductsAsync()
        {
            // 从数据库获取商品信息
            await foreach (var product from FetchFromDatabaseAsync())
            {
                yield return product;
            }
    
            // 从外部API获取商品信息
            await foreach (var product from FetchFromApiAsync())
            {
                yield return product;
            }
        }
    
        private async IAsyncEnumerable<ProductInfo> FetchFromDatabaseAsync()
        {
            using (var connection = new SqlClientConnection(_connectionString))
            {
                await connection.OpenAsync();
                var command = new SqlCommand("SELECT Name, Price FROM Products", connection);
                using (var reader = await command.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        yield return new ProductInfo
                        {
                            Name = reader.GetString(0),
                            Price = reader.GetDecimal(1)
                        };
                    }
                }
            }
        }
    
        private async IAsyncEnumerable<ProductInfo> FetchFromApiAsync()
        {
            var response = await _httpClient.GetAsync("https://example.com/api/products");
            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                // 假设这里使用Json.NET解析JSON数据
                var products = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ProductInfo>>(json);
                foreach (var product in products)
                {
                    yield return product;
                }
            }
        }
    }
    
  2. 处理并发问题

    • 使用锁机制:如果不同数据源获取的数据可能会相互影响,例如存在数据更新操作,可以使用lock关键字或SemaphoreSlim等同步工具。例如,如果数据库和API返回的数据可能存在重叠且需要修改,在修改共享数据时可以使用lock
    private static readonly object _lockObject = new object();
    private async IAsyncEnumerable<ProductInfo> FetchFromDatabaseAsync()
    {
        using (var connection = new SqlClientConnection(_connectionString))
        {
            await connection.OpenAsync();
            var command = new SqlCommand("SELECT Name, Price FROM Products", connection);
            using (var reader = await command.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    lock (_lockObject)
                    {
                        yield return new ProductInfo
                        {
                            Name = reader.GetString(0),
                            Price = reader.GetDecimal(1)
                        };
                    }
                }
            }
        }
    }
    
    • 线程安全的数据结构:如果需要在多个异步操作间共享数据,可以使用线程安全的数据结构,如ConcurrentDictionary。例如,如果要统计不同数据源获取的商品数量:
    private readonly ConcurrentDictionary<string, int> _productCount = new ConcurrentDictionary<string, int>();
    private async IAsyncEnumerable<ProductInfo> FetchFromDatabaseAsync()
    {
        using (var connection = new SqlClientConnection(_connectionString))
        {
            await connection.OpenAsync();
            var command = new SqlCommand("SELECT Name, Price FROM Products", connection);
            using (var reader = await command.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    var product = new ProductInfo
                    {
                        Name = reader.GetString(0),
                        Price = reader.GetDecimal(1)
                    };
                    _productCount.AddOrUpdate(product.Name, 1, (key, oldValue) => oldValue + 1);
                    yield return product;
                }
            }
        }
    }
    
  3. 处理数据一致性问题

    • 事务处理(针对数据库):如果从数据库获取商品信息涉及多个操作,并且需要保证这些操作的原子性,可以使用事务。例如,如果要在获取商品信息后更新商品的访问次数:
    private async IAsyncEnumerable<ProductInfo> FetchFromDatabaseAsync()
    {
        using (var connection = new SqlClientConnection(_connectionString))
        {
            await connection.OpenAsync();
            using (var transaction = connection.BeginTransaction())
            {
                try
                {
                    var command = new SqlCommand("SELECT Name, Price FROM Products", connection);
                    command.Transaction = transaction;
                    using (var reader = await command.ExecuteReaderAsync())
                    {
                        while (await reader.ReadAsync())
                        {
                            var product = new ProductInfo
                            {
                                Name = reader.GetString(0),
                                Price = reader.GetDecimal(1)
                            };
                            // 更新商品访问次数
                            var updateCommand = new SqlCommand("UPDATE Products SET VisitCount = VisitCount + 1 WHERE Name = @Name", connection);
                            updateCommand.Transaction = transaction;
                            updateCommand.Parameters.AddWithValue("@Name", product.Name);
                            await updateCommand.ExecuteNonQueryAsync();
                            yield return product;
                        }
                    }
                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    throw;
                }
            }
        }
    }
    
    • 版本控制:对于外部API获取的数据,可以在数据中添加版本号字段。在更新数据时,先比较版本号,如果版本号不一致则说明数据已被其他操作修改,需要重新获取或进行相应处理。例如,假设API返回的数据结构如下:
    public class ProductInfoWithVersion
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Version { get; set; }
    }
    private async IAsyncEnumerable<ProductInfoWithVersion> FetchFromApiAsync()
    {
        var response = await _httpClient.GetAsync("https://example.com/api/products");
        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync();
            var products = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ProductInfoWithVersion>>(json);
            foreach (var product in products)
            {
                yield return product;
            }
        }
    }
    

    当需要更新数据时,先获取当前版本号,更新时带上版本号进行条件更新,如果更新失败(版本号不一致),则重新获取数据。

    public async Task UpdateProductAsync(ProductInfoWithVersion product)
    {
        var updateData = new
        {
            product.Name,
            product.Price,
            Version = product.Version
        };
        var response = await _httpClient.PutAsJsonAsync($"https://example.com/api/products/{product.Name}", updateData);
        if (!response.IsSuccessStatusCode)
        {
            // 处理版本冲突,重新获取数据
            var newProduct = await FetchFromApiAsync().FirstOrDefaultAsync(p => p.Name == product.Name);
            if (newProduct != null)
            {
                // 重新更新或其他处理
            }
        }
    }