.net 异步编程task,async 实现简单基础的异步方法。并行方法 电脑版发表于:2025/9/29 9:50 [TOC] ### 实现简单基础的异步方法的两种写法 #### 写法1: ``` public Task<TransactionsStatisticsDto> GetTransactionsStatistics(GetTransactionsDto input) { TransactionsStatisticsDto transactionsStatisticsDto = new TransactionsStatisticsDto(); transactionsStatisticsDto.SumInbound = 109; transactionsStatisticsDto.ItemCollection = 129; return Task.Run(() => { return transactionsStatisticsDto; }); } ``` #### 写法2: ``` public Task<TransactionsStatisticsDto> GetTransactionsStatistics2(GetTransactionsDto input) { TransactionsStatisticsDto transactionsStatisticsDto = new TransactionsStatisticsDto(); transactionsStatisticsDto.SumInbound = 109; transactionsStatisticsDto.ItemCollection = 129; return Task.FromResult(transactionsStatisticsDto); } ``` #### 两种写法的区别 这两种写法虽然都能返回Task<TransactionsStatisticsDto>,但在执行机制、性能和适用场景上有显著区别: **1. 执行方式与线程** - Task.Run 版本: - 会将 lambda 表达式中的代码放入线程池线程执行 - 即使代码本身是同步的,也会触发线程切换 - 本质上是 "将同步操作异步化"(包装到线程池任务中) - Task.FromResult 版本: - 直接返回一个已完成的任务(CompletedTask) - 不会创建新线程,也不会触发线程切换 - 整个操作在当前调用线程同步执行 **2. 性能差异** - Task.FromResult 效率更高,因为: - 不涉及线程池调度开销 - 没有上下文切换成本 - 内部使用预缓存的已完成任务实例 - Task.Run 有额外开销: - 需要在线程池调度任务 - 可能导致不必要的线程切换 - 对于简单操作,性能损耗更明显 **3. 适用场景** - 使用 Task.FromResult: - 当方法逻辑本身是同步的,但需要满足异步接口契约 - 作为异步方法的 "快速路径"(如缓存命中场景) - 返回已知结果且无需实际异步操作时 - 使用 Task.Run: - 确实需要将 CPU 密集型同步操作移至后台线程时 - 避免长时间阻塞 UI 线程或 ASP.NET 请求线程 - 注意:在 ASP.NET 环境中通常不推荐使用 Task.Run,因为它会消耗额外线程池资源 **4. 对调用者的影响** - 两种方式对调用者来说用法相同(都可以用 await 调用) - 但 Task.FromResult 返回的任务立即处于完成状态,await 会同步继续执行 - Task.Run 返回的任务可能需要等待线程池调度,存在微小延迟 #### 用法推荐 在该的示例中,由于没有实际的异步操作或耗时计算,Task.FromResult 是更优选择,它能以最低成本满足异步方法契约。 只有当需要执行真正的后台操作时,才考虑使用 Task.Run。 #### 使用 Task.FromResult的优势,意义 它的主要价值不是提升单个请求的执行效率,而是满足异步编程模型的契约: - 当接口定义为异步(返回Task<T>)时,即使实现是同步的,也能符合接口要求 - 允许方法在未来无缝升级为真正的异步实现(例如后续需要添加数据库操作) - 保持调用方代码的一致性(始终使用await调用,无需区分同步 / 异步实现) Task.FromResult方法:虽然内部逻辑同样是同步执行,但由于遵循异步模式,在方法执行完毕后会立即释放线程(没有额外的线程切换开销)。在ASP.NET Core 的同步上下文(SynchronizationContext)下,这种方式对线程的利用率更高效。 高并发场景:Task.FromResult方式更有优势,因为它能更好地配合ASP.NET的线程池管理,减少线程阻塞时间,提高并发处理能力。 在ASP.NET开发中,推荐优先采用异步方法签名(即使内部用Task.FromResult),因为它能更好地适应高并发场景,并且符合现代.NET Web 开发的最佳实践。 ### 理解真正的异步方法与伪异步 要理解真正的异步方法实现,我们需要从底层 I/O 操作的异步性说起。以数据库操作为例(类似 SqlSugar 的ToListAsync()),真正的异步方法会利用操作系统的异步 I/O 能力,在等待数据返回时释放线程,而不是阻塞线程。 #### 下面是一个模拟数据库查询异步实现的示例,展示了异步方法的核心原理: ``` using System; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; // 模拟实体类 public class Transaction { public int Id { get; set; } public decimal Amount { get; set; } } // 模拟异步数据库操作 public class TransactionRepository { private readonly string _connectionString; public TransactionRepository(string connectionString) { _connectionString = connectionString; } // 模拟SqlSugar的ToListAsync()类似实现 public async Task<List<Transaction>> GetTransactionsAsync( string sql, CancellationToken cancellationToken = default) { // 1. 创建连接(同步操作,但开销小) using (var connection = new SqlConnection(_connectionString)) { // 2. 异步打开连接(真正的异步I/O操作) await connection.OpenAsync(cancellationToken).ConfigureAwait(false); // 3. 创建命令 using (var command = new SqlCommand(sql, connection)) { // 4. 异步执行查询并获取读取器(核心异步操作) using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false)) { var result = new List<Transaction>(); // 5. 异步读取数据(每次读取都是异步操作) while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { result.Add(new Transaction { Id = reader.GetInt32(0), Amount = reader.GetDecimal(1) }); } return result; } } } } } // 使用示例 public class TransactionService { private readonly TransactionRepository _repository; public TransactionService(TransactionRepository repository) { _repository = repository; } public async Task<List<Transaction>> GetRecentTransactionsAsync() { // 调用异步方法 return await _repository.GetTransactionsAsync( "SELECT Id, Amount FROM Transactions WHERE Date > GETDATE() - 7"); } } ``` #### 真正异步方法的核心原理: - **基于 I/O 完成端口(IOCP)**: 像OpenAsync()、ExecuteReaderAsync()这些方法底层调用了操作系统的异步 I/O 接口,当数据库操作正在执行时,.NET 线程会被释放回线程池,不会被阻塞等待。 - **非阻塞等待**: 当数据库返回结果时,操作系统会通知.NET 运行时,运行时从线程池取一个空闲线程来处理后续逻辑(如读取数据、转换结果)。 - **ConfigureAwait(false)的作用**: 告诉运行时不需要回到原始上下文(如ASP.NET请求上下文),减少线程切换开销,提高性能。这在类库实现中非常常见。 - **CancellationToken 支持**: 允许在操作完成前取消请求,这在处理超时或用户取消操作时非常有用。 #### 与伪异步的本质区别: - **真正的异步**:如上面的示例,利用底层 I/O 操作的异步性,全程不阻塞线程 - **伪异步**:用Task.Run()包装同步方法,只是把同步操作移到了线程池线程,仍然会阻塞一个线程