.NET中高效处理数据的类型与管道技术
立即解锁
发布时间: 2025-09-15 00:32:08 阅读量: 2 订阅数: 1 AIGC 

### .NET 中高效处理数据的类型与管道技术
#### 1. Span<T> 类型及其实用方法
Span<T> 自引入以来得到了越来越多的支持,其应用场景也在不断拓展。除了类似数组的索引器和 Length 属性外,Span<T> 还提供了一些实用方法:
- **Clear 和 Fill 方法**:用于将 Span 中的所有元素初始化为元素类型的默认值或特定值。不过,这些方法在 ReadOnlySpan<T> 中不可用。
- **ToArray 方法**:当需要将 Span 的内容传递给一个需要数组的方法时,可以使用该方法。虽然在这种情况下无法避免内存分配,但它提供了一种可行的解决方案。
- **TryCopyTo 方法**:可用于在不同的 Span 之间复制数据。该方法能够处理源和目标 Span 引用同一容器内重叠范围的情况。如果目标 Span 太小,该方法将返回 false。
Span<T> 和 ReadOnlySpan<T> 被声明为 ref struct 类型,这意味着它们只能存在于栈上。因此,不能在类或非 ref struct 的结构体中使用 Span 类型的字段。此外,在异步方法、匿名函数和迭代器方法中使用 Span 也存在限制。不过,可以在局部方法中使用它们,甚至可以在外部方法中声明 ref struct 变量并在嵌套方法中使用,但不能创建引用该局部方法的委托,否则会导致编译器将共享变量移动到堆上的对象中。
#### 2. Memory<T> 类型
Memory<T> 和 ReadOnlyMemory<T> 与 Span<T> 和 ReadOnlySpan<T> 代表相同的基本概念,它们提供了对连续元素序列的统一视图,这些元素可以存在于数组、非托管内存或字符串(如果元素类型是 char)中。与 Span 不同的是,Memory 不是 ref struct 类型,因此可以在任何地方使用,但它的性能不如 Span。可以将 Memory<T> 转换为 Span<T>,但转换会有一定的成本,因此在循环中读写 Memory<T> 中的元素时,应在循环外部将其转换为 Span<T>,以提高性能。
#### 3. ReadOnlySequence<T> 类型
前面介绍的类型都代表连续的内存块,但数据并不总是以最方便的形式呈现。例如,在繁忙的服务器上处理并发请求时,网络消息可能会交错,导致请求内容被分割成多个内存块。为了处理这种情况,.NET 提供了 ReadOnlySequence<T> 类型,用于表示概念上是单个序列但已被分割成多个范围的数据。该类型只有只读形式,因为在处理碎片化数据时,通常是作为读者,而不是生产者。
#### 4. 使用管道处理数据流
为了实现对大量数据的安全、高效处理,需要考虑数据如何进入内存。System.Io.Pipelines NuGet 包提供了一套高性能的系统,用于从数据源加载数据,并将其传递给使用 Span 进行处理的代码。管道处理的核心是 Pipe 类,它有两个属性:Writer 和 Reader。Writer 返回一个 PipeWriter,用于将数据加载到内存中;Reader 返回一个 PipeReader,通常是代码与之交互的部分。
从管道读取数据的基本过程如下:
1. 调用 PipeReader.ReadAsync 方法,该方法返回一个任务。如果当前没有可用数据,需要等待数据源提供数据。
2. 当数据可用时,任务将提供一个 ReadResult 对象,其中包含一个 ReadOnlySequence<T>,该序列将可用数据表示为一个或多个 ReadOnlySpan<T> 值。
3. 处理尽可能多的可用数据。
4. 调用 Reader 的 AdvanceTo 方法,告知它代码已经处理了多少数据。
5. 检查 ReadResult.IsComplete 属性,如果为 false,则重复上述步骤。
在处理数据时,可能会遇到数据被分割的情况。例如,一个大的 JSON 消息可能会被分割成多个网络数据包,导致数据在内存中不连续。为了处理这种情况,可以采取以下两种方法:
- 方法一:读取数据的代码维护足够的状态,以便在序列中的任何点停止并稍后重新启动。
- 方法二:使用 PipeReader 的 AdvanceTo 方法,告知它已经处理了部分数据。下次调用 ReadAsync 时,ReadResult.Buffer 将包含之前未处理的数据。
以下是处理数据的流程表格:
|步骤|操作|
|----|----|
|1|调用 PipeReader.ReadAsync 等待数据|
|2|获取 ReadResult 对象,包含 ReadOnlySequence<T>|
|3|处理数据|
|4|调用 AdvanceTo 告知处理进度|
|5|检查 IsComplete 属性,决定是否继续|
#### 5. 处理 JSON 的示例
在 ASP.NET Core 中处理包含 JSON 的 HTTP 请求时,通常的做法是使用 [FromBody] 属性将请求体中的数据映射到对象中。但这种方法会导致 ASP.NET Core 为每个请求分配多个对象,增加了内存开销。为了避免这些分配,可以使用前面介绍的技术。
以下是一个典型的处理 JSON 请求的示例:
```csharp
[HttpPost]
[Route("/jobs/create")]
public void CreateJob([FromBody] JobDescription requestBody)
{
switch (requestBody.JobCategory)
{
case "arduous":
CreateArduousJob(requestBody.DepartmentId);
break;
case "tedious":
CreateTediousJob(requestBody.DepartmentId);
break;
}
}
public record JobDescription(int DepartmentId, string JobCategory);
```
为了避免内存分配,可以使用 PipeReader 和 Utf8JsonReader 来直接处理请求体中的数据:
```csharp
private static readonly byte[] Utf8TextJobCategory =
Encoding.UTF8.GetBytes("JobCategory");
private static readonly byte[] Utf8TextDepartmentId =
Encoding.UTF8.GetBytes("DepartmentId");
private static readonly byte[] Utf8TextArduous = Encoding.UTF8.GetBytes("arduou
```
0
0
复制全文
相关推荐









