面试题答案
一键面试-
使用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; } } } }
-
处理并发问题
- 使用锁机制:如果不同数据源获取的数据可能会相互影响,例如存在数据更新操作,可以使用
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; } } } }
- 使用锁机制:如果不同数据源获取的数据可能会相互影响,例如存在数据更新操作,可以使用
-
处理数据一致性问题
- 事务处理(针对数据库):如果从数据库获取商品信息涉及多个操作,并且需要保证这些操作的原子性,可以使用事务。例如,如果要在获取商品信息后更新商品的访问次数:
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) { // 重新更新或其他处理 } } }