37. 异步编程与并发(Async/Await)
异步编程是 C# 中提高性能和响应性的关键技术,尤其在需要进行 I/O 操作(如文件读取、网络请求)时。async
和 await
使得编写异步代码变得更加简单和直观。
1. 基本的异步方法
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// 调用异步方法
Console.WriteLine("Before Async Operation");
await PerformAsyncTask();
Console.WriteLine("After Async Operation");
}
static async Task PerformAsyncTask()
{
// 模拟一个异步任务,延迟 2 秒
await Task.Delay(2000);
Console.WriteLine("Async Operation Completed");
}
}
解释:
async
关键字标记一个方法为异步方法,这意味着方法可以执行异步操作并返回Task
或Task<T>
。await
用于等待异步操作完成,在等待时,主线程不会被阻塞,可以执行其他任务。Task.Delay(2000)
模拟一个异步操作,表示等待 2 秒钟。
2. 异步并发执行多个任务
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Task task1 = Task.Run(() => PerformAsyncTask("Task 1"));
Task task2 = Task.Run(() => PerformAsyncTask("Task 2"));
// 等待任务完成
await Task.WhenAll(task1, task2);
Console.WriteLine("Both tasks completed.");
}
static async Task PerformAsyncTask(string taskName)
{
await Task.Delay(2000); // 模拟异步工作
Console.WriteLine($"{taskName} completed.");
}
}
解释:
- 使用
Task.Run()
启动多个并发任务。WhenAll
用于等待所有任务完成。 - 两个异步任务同时运行,不会阻塞主线程。
Task.Delay(2000)
代表任务的执行时间,实际应用中可以替换为网络请求、数据库操作等。
38. Web API 开发(使用 ASP.NET Core)
ASP.NET Core 是一个跨平台的 Web 框架,允许你用 C# 开发 RESTful Web API。它支持高效的处理 HTTP 请求,提供了强大的路由、依赖注入、认证等功能。
1. 创建一个简单的 Web API
首先,在命令行中创建一个新的 Web API 项目:
dotnet new webapi -n MyWebApi
cd MyWebApi
dotnet run
这将启动一个 Web API 项目,默认会有一个 WeatherForecastController
。
2. 编写一个简单的 API 控制器
using Microsoft.AspNetCore.Mvc;
namespace MyWebApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET api/my
[HttpGet]
public IActionResult Get()
{
return Ok(new { message = "Hello, World!" });
}
// GET api/my/5
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok(new { message = $"You requested item {id}" });
}
}
}
解释:
ApiController
和Route
特性用于标记控制器和定义路由。IActionResult
用于返回不同类型的响应。Ok()
方法表示返回 200 状态码和一个 JSON 对象。
3. 测试 API
你可以使用 Postman 或直接通过浏览器访问 API,例如:
GET http://localhost:5000/api/my
会返回{ "message": "Hello, World!" }
GET http://localhost:5000/api/my/5
会返回{ "message": "You requested item 5" }
39. 依赖注入与中间件(Middleware)
ASP.NET Core 中有一套强大的依赖注入(DI)机制,以及用于请求管道的中间件系统。
1. 配置依赖注入
在 Startup.cs
或 Program.cs
文件中配置依赖注入:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<MyMiddleware>();
}
}
2. 自定义中间件
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// 在请求管道中执行一些操作
await context.Response.WriteAsync("Hello from MyMiddleware!\n");
// 调用下一个中间件
await _next(context);
}
}
解释:
- 依赖注入通过
ConfigureServices
方法完成,所有的依赖会自动注入到构造函数中。 - 中间件是一个函数链,用于处理 HTTP 请求和响应。可以在中间件中添加认证、日志记录等功能。
40. 性能分析和调优
C# 提供了多种性能分析工具,帮助开发者定位性能瓶颈,优化应用程序的响应速度和吞吐量。
1. 使用 dotnet-counters
监控应用程序性能
dotnet-counters
是 .NET 提供的一个命令行工具,用于监控应用程序的性能。可以实时显示 CPU 使用率、内存分配、垃圾回收等信息。
dotnet-counters monitor --process-id <PID>
2. 使用 Profiler 进行性能分析
你还可以使用 Visual Studio 的性能分析工具,或第三方工具(如 JetBrains dotTrace)来分析应用程序的性能,查看 CPU 和内存的使用情况,找出性能瓶颈。
41. 设计模式与 C#
设计模式是解决常见问题的最佳实践。C# 支持多种设计模式,如单例模式、工厂模式、观察者模式等。
1. 单例模式
单例模式保证某个类在应用程序中只有一个实例,并提供全局访问点。
public class Singleton
{
private static Singleton _instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
解释:
Singleton
类中定义了一个静态变量_instance
,并提供Instance
属性用于获取唯一实例。- 这种模式常用于管理共享资源,例如数据库连接或配置对象。
42. 异常处理与自定义异常
异常处理是编程中不可或缺的一部分,C# 提供了强大的异常处理机制。通过使用 try-catch
语句,你可以捕获和处理运行时的错误,并根据需要做出响应。
1. 基本的异常处理
using System;
class Program
{
static void Main()
{
try
{
int result = 10 / 0; // 会引发除零异常
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: Cannot divide by zero.");
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("This block always runs.");
}
}
}
解释:
try
块用于包裹可能会引发异常的代码。catch
块用于捕获特定类型的异常,并提供处理逻辑。finally
块无论是否发生异常都会执行,常用于资源释放等操作。
2. 自定义异常类
有时你需要创建自己的异常类型,来表达特定的错误情况。例如,在某个业务逻辑中,可能会遇到特定的条件需要抛出一个自定义的异常。
using System;
class Program
{
static void Main()
{
try
{
throw new MyCustomException("This is a custom exception!");
}
catch (MyCustomException ex)
{
Console.WriteLine($"Caught custom exception: {ex.Message}");
}
}
}
public class MyCustomException : Exception
{
public MyCustomException(string message) : base(message) { }
}
解释:
- 自定义异常类继承自
Exception
类,并通过构造函数传递消息。 throw
关键字用于抛出异常,捕获时会进入catch
块。
43. 泛型(Generics)
泛型使得你可以在不确定数据类型的情况下编写类、接口和方法。它提供了类型安全的同时,还能避免重复代码。
1. 泛型方法
using System;
class Program
{
static void Main()
{
int intResult = Add(10, 20);
Console.WriteLine("Integer Result: " + intResult);
double doubleResult = Add(10.5, 20.3);
Console.WriteLine("Double Result: " + doubleResult);
}
static T Add<T>(T a, T b)
{
return (dynamic)a + (dynamic)b; // 使用 dynamic 实现泛型加法
}
}
解释:
Add<T>
方法是一个泛型方法,使用类型参数T
来处理不同类型的参数。- 使用
dynamic
来处理类型不同的加法操作,因为泛型无法直接进行加法操作。
2. 泛型类
using System;
class Program
{
static void Main()
{
var intBox = new Box<int>(100);
Console.WriteLine("Box contains: " + intBox.Item);
var stringBox = new Box<string>("Hello, world!");
Console.WriteLine("Box contains: " + stringBox.Item);
}
}
public class Box<T>
{
public T Item { get; set; }
public Box(T item)
{
Item = item;
}
}
解释:
Box<T>
是一个泛型类,它可以存储任何类型的对象。通过构造函数来初始化。- 通过泛型类的方式,你可以避免为每种类型编写重复的代码。
44. LINQ(Language Integrated Query)
LINQ 是 C# 提供的一种强大的查询语言,它允许你以声明式的方式对集合进行查询和操作。LINQ 可以用于数组、集合、数据库、XML 等数据源。
1. 使用 LINQ 查询集合
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = from number in numbers
where number % 2 == 0
select number;
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
}
}
解释:
- LINQ 查询表达式的基本语法包括
from
、where
、select
等关键字,用于筛选、排序和选择集合元素。 - 在本例中,
evenNumbers
包含了所有偶数,并通过foreach
循环输出。
2. 使用 LINQ 方法链
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers.Where(n => n % 2 == 0)
.Select(n => n * n);
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
}
}
解释:
- LINQ 方法链通过
Where
和Select
等方法提供链式调用,可以灵活地对集合进行操作。 - 这里的例子中,
Where
筛选偶数,Select
对偶数进行平方处理。
45. 多线程与并发
多线程编程允许你同时执行多个任务,从而提高程序的效率和响应性。在 C# 中,可以通过 Task
和 Thread
来实现并发。
1. 使用 Task
实现多线程
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// 启动两个并发任务
Task task1 = Task.Run(() => PrintNumbers(1));
Task task2 = Task.Run(() => PrintNumbers(2));
// 等待任务完成
await Task.WhenAll(task1, task2);
}
static void PrintNumbers(int taskNumber)
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"Task {taskNumber}: {i}");
}
}
}
解释:
Task.Run()
用于启动异步任务,WhenAll
用于等待所有任务完成。- 这里的例子启动了两个并发的任务,它们同时输出不同的数字。
2. 使用 Parallel
类进行并行操作
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Parallel.For(1, 6, i =>
{
Console.WriteLine($"Number {i}");
});
}
}
解释:
Parallel.For
可以并行执行一个循环,它会将循环体的迭代分配给多个线程,提高效率。- 这种方式特别适用于不依赖顺序的任务,能够充分利用多核 CPU。
46. 内存管理与垃圾回收
C# 是一种托管语言,这意味着它使用垃圾回收(GC)机制来自动管理内存。了解垃圾回收的工作原理,可以帮助你更好地管理应用程序的内存使用。
1. 强制垃圾回收
虽然垃圾回收是自动的,但你可以通过 GC.Collect()
强制触发垃圾回收:
using System;
class Program
{
static void Main()
{
// 创建一个大型对象
var largeObject = new byte[10_000_000];
// 强制垃圾回收
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Garbage collection triggered.");
}
}
解释:
GC.Collect()
用于手动触发垃圾回收。通常情况下不需要手动调用,因为 .NET 会自动处理。GC.WaitForPendingFinalizers()
等待所有的终结器(finalizer)执行完毕。
2. 释放非托管资源
如果你的应用程序使用了非托管资源(例如文件句柄、数据库连接等),需要显式释放它们。这通常通过实现 IDisposable
接口来完成。
using System;
public class ResourceHandler : IDisposable
{
private bool _disposed = false;
// 实现 IDisposable 接口
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
_disposed = true;
}
}
~ResourceHandler()
{
Dispose(false);
}
}
class Program
{
static void Main()
{
using (var resource = new ResourceHandler())
{
// 使用资源
}
}
}
解释:
IDisposable
接口提供了Dispose()
方法,用于释放资源。GC.SuppressFinalize()
用于防止垃圾回收器调用终结器(~ResourceHandler
),因为资源已经释放。using
语句确保在使用完资源后会自动调用Dispose()
。