-
C#中关于异步性能提升——减少线程阻塞
第十部分:性能优化与内存管理
实例99:异步性能提升——减少线程阻塞
实例介绍
之前做电商支付系统时,用户反馈支付接口响应时间超过3秒,通过性能分析发现,接口中同步调用了第三方支付API和数据库查询,导致线程阻塞,系统吞吐量从100QPS降到20QPS。通过异步改造后,接口响应时间降到500ms以内,系统吞吐量提升到500QPS,性能提升25倍。这节就带你从零学习异步性能优化,包括异步编程基础、避免线程阻塞、异步数据库操作、异步HTTP调用,覆盖异vb.net教程C#教程python教程SQL教程access 2010教程步性能提升的核心场景。
需求分析
异步性能提升要解决“线程阻塞、系统吞吐量低、响应时间长”的问题,具体需求如下:
1.线程阻塞避免:识别并避免同步调用导致的线程阻塞;
2.异步编程实现:学习异步编程的基本语法和最佳实践;
3.异步数据库操作:使用EF Core异步API提升数据库操作性能;
4.异步HTTP调用:使用HttpClient异步API提升HTTP调用性能;
5.系统吞吐量提升:通过异步编程提升系统吞吐量,减少资源占用;
代码实现
前置条件:.NET 8 SDK;Visual Studio 2022;SQL Server 2022;
场景1:线程阻塞识别——同步调用导致的性能瓶颈
同步调用第三方API和数据库查询,导致线程阻塞,系统吞吐量低。
步骤1:错误代码(ThreadBlockingExample.cs)
csharp
using System;
using System.Data.SqlClient;
using System.Net.Http;
using System.Threading.Tasks;
namespace AsyncPerformanceOptimization;
public class PaymentService
{
private readonly HttpClient _httpClient;
private readonly string _connectionString = "Server=.;Database=PaymentDb;Integrated Security=True;TrustServerCertificate=True;";
public PaymentService()
{
_httpClient = new HttpClient();
}
// 错误:同步调用导致线程阻塞,系统吞吐量低
public string ProcessPayment(PaymentRequest request)
{
// 1. 同步查询用户余额(阻塞线程)
var balance = GetUserBalance(request.UserId);
if (balance < request.Amount)
{
return "余额不足";
}
// 2. 同步调用第三方支付API(阻塞线程)
var paymentResult = CallPaymentGateway(request);
if (!paymentResult.Success)
{
return "支付失败:" + paymentResult.Message;
}
// 3. 同步更新用户余额(阻塞线程)
UpdateUserBalance(request.UserId, balance - request.Amount);
return "支付成功";
}
// 同步数据库查询
private decimal GetUserBalance(int userId)
{
using var connection = new SqlConnection(_connectionString);
connection.Open(); // 阻塞线程,等待数据库响应
using var command = new SqlCommand("SELECT Balance FROM Users WHERE UserId = @UserId", connection);
command.Parameters.AddWithValue("@UserId", userId);
var result = command.ExecuteScalar(); // 阻塞线程,等待数据库返回结果
return result != null ? (decimal)result : 0;
}
// 同步HTTP调用
private PaymentResult CallPaymentGateway(PaymentRequest request)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("OrderId", request.OrderId),
new KeyValuePair<string, string>("Amount", request.Amount.ToString()),
new KeyValuePair<string, string>("UserId", request.UserId.ToString())
});
var response = _httpClient.PostAsync("https://api.paymentgateway.com/pay", content).Result; // 阻塞线程,等待HTTP响应
response.EnsureSuccessStatusCode();
var responseContent = response.Content.ReadAsStringAsync().Result; // 阻塞线程,等待响应内容
return Newtonsoft.Json.JsonConvert.DeserializeObject<PaymentResult>(responseContent);
}
// 同步数据库更新
private void UpdateUserBalance(int userId, decimal newBalance)
{
using var connection = new SqlConnection(_connectionString);
connection.Open(); // 阻塞线程,等待数据库响应
using var command = new SqlCommand("UPDATE Users SET Balance = @NewBalance WHERE UserId = @UserId", connection);
command.Parameters.AddWithValue("@NewBalance", newBalance);
command.Parameters.AddWithValue("@UserId", userId);
command.ExecuteNonQuery(); // 阻塞线程,等待数据库更新完成
}
}
public class PaymentRequest
{
public string OrderId { get; set; } = string.Empty;
public int UserId { get; set; }
public decimal Amount { get; set; }
}
public class PaymentResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}
步骤2:性能分析(Visual Studio性能探查器)
1.按F5启动调试,使用性能探查器分析接口性能;
2.查看“线程”选项卡,发现大量线程处于“等待”状态;
3.查看“CPU使用率”,发现CPU使用率只有10%,说明线程被阻塞,CPU资源未充分利用;
场景2:异步改造——避免线程阻塞,提升系统吞吐量
使用异步编程改造同步代码,避免线程阻塞,提升系统吞吐量。
步骤1:修复代码(AsyncPaymentService.cs)
csharp
using System;
using System.Data.SqlClient;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace AsyncPerformanceOptimization;
public class AsyncPaymentService
{
private readonly HttpClient _httpClient;
private readonly PaymentDbContext _dbContext;
public AsyncPaymentService(HttpClient httpClient, PaymentDbContext dbContext)
{
_httpClient = httpClient;
_dbContext = dbContext;
}
// 正确:异步方法,避免线程阻塞,提升系统吞吐量
public async Task<string> ProcessPaymentAsync(PaymentRequest request)
{
// 1. 异步查询用户余额(非阻塞线程)
var balance = await GetUserBalanceAsync(request.UserId);
if (balance < request.Amount)
{
return "余额不足";
}
// 2. 异步调用第三方支付API(非阻塞线程)
var paymentResult = await CallPaymentGatewayAsync(request);
if (!paymentResult.Success)
{
return "支付失败:" + paymentResult.Message;
}
// 3. 异步更新用户余额(非阻塞线程)
await UpdateUserBalanceAsync(request.UserId, balance - request.Amount);
return "支付成功";
}
// 异步数据库查询(EF Core异步API)
private async Task<decimal> GetUserBalanceAsync(int userId)
{
// 使用EF Core异步API,非阻塞线程
var user = await _dbContext.Users
.FirstOrDefaultAsync(u => u.UserId == userId);
return user?.Balance ?? 0;
}
// 异步HTTP调用(HttpClient异步API)
private async Task<PaymentResult> CallPaymentGatewayAsync(PaymentRequest request)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("OrderId", request.OrderId),
new KeyValuePair<string, string>("Amount", request.Amount.ToString()),
new KeyValuePair<string, string>("UserId", request.UserId.ToString())
});
// 使用HttpClient异步API,非阻塞线程
var response = await _httpClient.PostAsync("https://api.paymentgateway.com/pay", content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync(); // 异步读取响应内容
return Newtonsoft.Json.JsonConvert.DeserializeObject<PaymentResult>(responseContent);
}
// 异步数据库更新(EF Core异步API)
private async Task UpdateUserBalanceAsync(int userId, decimal newBalance)
{
var user = await _dbContext.Users
.FirstOrDefaultAsync(u => u.UserId == userId);
if (user != null)
{
user.Balance = newBalance;
await _dbContext.SaveChangesAsync(); // 异步保存更改
}
}
}
public class PaymentDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public PaymentDbContext(DbContextOptions<PaymentDbContext> options) : base(options)
{
}
}
public class User
{
public int UserId { get; set; }
public string Username { get; set; } = string.Empty;
public decimal Balance { get; set; }
}
步骤2:ASP.NET Core异步接口(PaymentController.cs)
csharp
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace AsyncPerformanceOptimization;
[ApiController]
[Route("api/[controller]")]
public class PaymentController : ControllerBase
{
private readonly AsyncPaymentService _paymentService;
public PaymentController(AsyncPaymentService paymentService)
{
_paymentService = paymentService;
}
// 异步API接口,非阻塞线程,提升系统吞吐量
[HttpPost("process")]
public async Task<IActionResult> ProcessPayment([FromBody] PaymentRequest request)
{
var result = await _paymentService.ProcessPaymentAsync(request);
return Ok(new { Result = result });
}
}
场景3:异步编程最佳实践——避免常见陷阱
避免异步编程中的常见陷阱,如异步同步混用、未正确处理异常。
步骤1:错误代码(AsyncPitfallsExample.cs)
csharp
using System;
using System.Threading.Tasks;
namespace AsyncPerformanceOptimization;
public class AsyncPitfallsService
{
// 错误:异步方法中同步调用.Result或.Wait(),导致线程死锁
public async Task<string> GetDataAsync()
{
// 异步方法
var dataTask = FetchDataAsync();
// 同步调用.Result,导致线程死锁(在ASP.NET Core中不会死锁,但会阻塞线程)
var data = dataTask.Result;
return data;
}
// 错误:异步方法没有使用await,导致编译器警告,方法同步执行
public async Task<string> FetchDataAsync()
{
// 没有使用await,方法同步执行
return "Data from server";
}
// 错误:未正确处理异步方法的异常
public async Task ProcessDataAsync()
{
try
{
// 异步调用
await FetchDataAsync();
}
catch (Exception ex)
{
// 未正确记录异常或处理异常
Console.WriteLine("Error: " + ex.Message);
}
}
}
步骤2:修复代码(AsyncBestPracticesExample.cs)
csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace AsyncPerformanceOptimization;
public class AsyncBestPracticesService
{
private readonly ILogger<AsyncBestPracticesService> _logger;
public AsyncBestPracticesService(ILogger<AsyncBestPracticesService> logger)
{
_logger = logger;
}
// 正确:异步方法中使用await,避免线程阻塞
public async Task<string> GetDataAsync()
{
var data = await FetchDataAsync();
return data;
}
// 正确:异步方法使用await,编译器无警告
public async Task<string> FetchDataAsync()
{
// 模拟异步操作
await Task.Delay(100);
return "Data from server";
}
// 正确:正确处理异步方法的异常
public async Task ProcessDataAsync()
{
try
{
await FetchDataAsync();
}
catch (Exception ex)
{
// 记录异常,便于排查问题
_logger.LogError(ex, "Error processing data");
// 抛出异常或处理异常
throw;
}
}
}
逐行讲解
场景2:异步改造核心代码
1.async/await关键字:async关键字标记方法为异步方法,await关键字等待异步操作完成,非阻塞线程;
2.Task
3.EF Core异步API:FirstOrDefaultAsync、SaveChangesAsync等异步API,非阻塞线程,提升数据库操作性能;
4.HttpClient异步API:PostAsync、ReadAsStringAsync等异步API,非阻塞线程,提升HTTP调用性能;
场景3:异步最佳实践核心代码
1.避免.Result/.Wait():异步方法中使用await替代.Result或.Wait(),避免线程阻塞和死锁;
2.异步方法必须使用await:异步方法中必须使用await,否则方法会同步执行,编译器会发出警告;
3.异常处理:异步方法的异常可以使用try/catch捕获,和同步方法一样;
基础知识拓展
-
异步编程核心概念
线程阻塞:同步调用会导致线程阻塞,线程在等待操作完成时无法处理其他请求;
非阻塞线程:异步调用不会阻塞线程,线程在等待操作完成时可以处理其他请求;
任务调度:异步操作由任务调度器管理,任务调度器会在操作完成时回调继续执行;
上下文切换:异步编程会减少上下文切换,提升系统性能; - 异步编程最佳实践
| 实践 | 原因 | 好处 |
|---|---|---|
| async/await全程异步 | 避免异步同步混用,导致线程阻塞 | 提升系统吞吐量,减少资源占用 |
| 避免.Result/.Wait() | 避免线程阻塞和死锁 | 提升系统稳定性,避免性能瓶颈 |
| 使用ConfigureAwait(false) | 避免捕获上下文,提升异步操作性能 | 减少上下文切换,提升执行速度 |
| 异步方法命名以Async结尾 | 符合.NET命名规范,便于识别异步方法 | 提高代码可读性,避免误用 |
| 正确处理异常 | 异步方法的异常需要正确捕获和处理 | 提高系统稳定性,便于排查问题 |
-
异步性能对比
| 指标 | 同步调用 | 异步调用 |
| ---- | ---- | ---- |
| 线程阻塞 | 是,线程在等待时无法处理其他请求 | 否,线程在等待时可以处理其他请求 |
| 系统吞吐量 | 低,线程资源有限 | 高,线程资源可以充分利用 |
| 响应时间 | 长,多个同步调用串行执行 | 短,多个异步调用并行执行 |
| 资源占用 | 高,需要更多线程处理请求 | 低,少量线程即可处理大量请求 | -
异步编程适用场景
IO密集型任务:如数据库操作、HTTP调用、文件读写等,异步编程可以显著提升性能;
高并发场景:如API接口、微服务、消息队列等,异步编程可以提升系统吞吐量;
响应式系统:如实时数据处理、WebSocket通信等,异步编程可以提升系统响应速度; -
异步编程常见误区
异步同步混用:在异步方法中使用.Result或.Wait(),导致线程阻塞;
过度异步:在CPU密集型任务中使用异步编程,不会提升性能,反而会增加开销;
未正确处理异常:异步方法的异常未被捕获,导致系统崩溃;
异步方法未使用await:异步方法中没有使用await,方法同步执行,编译器发出警告;
总结
异步性能提升的核心是避免线程阻塞,充分利用线程资源,提升系统吞吐量。关键要点:
1.异步编程基础:掌握async/await关键字和Task返回类型;
2.线程阻塞避免:识别并避免同步调用导致的线程阻塞;
3.异步API使用:使用EF Core异步API和HttpClient异步API提升性能;
4.最佳实践遵循:遵循异步编程最佳实践,避免常见陷阱;
5.性能测试:使用性能测试工具(如JMeter、k6)验证异步改造效果;
比如这个异步性能优化,异步改造后,接口响应时间从3秒降到500ms以内,系统吞吐量从20QPS提升到500QPS,性能提升25倍。掌握异步性能优化,你就能构建高并发、高性能的.NET应用!
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49497.html










