admin管理员组

文章数量:1442757

C# 12 中的 Span<T> 和 Memory<T>:高级开发人员的性能助推器

作为ASP.NET开发人员,我们一直在寻找能让我们的Web应用程序运行得更快、更高效的方法。Span<T>Memory<T>这两个强大的工具就能帮我们达成这一目标。它们于几年前被引入,如今已成为编写高性能C#代码必不可少的部分。

让我们通过实际示例以及针对2024年C# 12的一些技巧,来探讨如何在ASP.NET应用程序中有效地使用Span<T>Memory<T>

Span<T>Memory<T>是什么?

让我们先从基础知识讲起:

Span<T>是一种表示连续内存块的类型。它可用于处理数组、字符串或非托管内存,而且无需创建副本。

Memory<T>Span<T>类似,但它可用于异步方法中,并且能存储在字段里。

可以把Span<T>想象成能直接操作的内存视图,而Memory<T>则是对该内存的引用,能更自由地传递。

下面让我们来看一些在常见的ASP.NET场景中,Span<T>Memory<T>能发挥重大作用的情况:

Span<T>

1. 解析请求数据

设想你正在构建一个会接收大量JSON数据的API。你可以使用Span<T>更高效地解析数据,而不必为每条数据都分配新的字符串:

代码语言:javascript代码运行次数:0运行复制
[HttpPost]
publicIActionResultProcessOrder([FromBody]string orderJson)
{
    ReadOnlySpan<char> jsonSpan = orderJson.AsSpan();
    
    // 在不分配新字符串的情况下查找 "totalAmount" 字段
    int startIndex = jsonSpan.IndexOf("\"totalAmount\":")+"\"totalAmount\":".Length;
    int endIndex = jsonSpan.Slice(startIndex).IndexOf(',');
    
    if(decimal.TryParse(jsonSpan.Slice(startIndex, endIndex),outdecimal totalAmount))
    {
        // 处理订单...
        returnOk($"Order processed with total amount: {totalAmount}");
    }
    
    returnBadRequest("Invalid order data");
}

这种方法减少了内存分配,提升了性能,尤其在处理大型有效载荷时效果显著。

2. 高效的响应编写

在发送响应(特别是大型响应)时,Span<T>有助于优化内存使用:

代码语言:javascript代码运行次数:0运行复制
[HttpGet]
publicIActionResultGetLargeData()
{
    constint bufferSize =*;// 1 MB缓冲区
    byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
    
    try
    {
        int dataSize =GenerateLargeData(buffer.AsSpan());
        returnFile(buffer.AsMemory(, dataSize).ToArray(),"application/octet-stream","large-data.bin");
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);
    }
}

privateintGenerateLargeData(Span<byte> buffer)
{
    // 用数据填充缓冲区...
    return/* 实际数据大小 */;
}

这个示例结合使用ArrayPool<T>Span<T>Memory<T>,能在不过度分配内存的情况下高效地处理大型数据。

3. 中间件中的URL解析

自定义中间件通常需要检查URL。以下展示了如何高效地进行这项操作:

代码语言:javascript代码运行次数:0运行复制
public classCustomUrlMiddleware
{
    privatereadonlyRequestDelegate _next;

    publicCustomUrlMiddleware(RequestDelegate next)=> _next = next;

    publicTaskInvokeAsync(HttpContext context)
{
        ReadOnlySpan<char> path = context.Request.Path.Value.AsSpan();
        
        if(path.StartsWith("/api/".AsSpan()))
        {
            // 处理API请求
            context.Items["IsApiRequest"]=true;
        }
        
        return_next(context);
    }
}

这个中间件在检查URL时无需分配新的字符串,这对于高流量应用程序来说非常有用。

Memory<T>

在需要以下操作时可使用Memory<T>

  • 跨异步边界处理内存。
  • 将对内存段的引用作为类中的字段进行存储。
  • 将内存引用传递给期望接收Memory<T>的方法。
1. 异步文件上传处理
代码语言:javascript代码运行次数:0运行复制
[HttpPost("upload")]
publicasyncTask<IActionResult>UploadFile(IFormFile file)
{
    if(file.Length >10_000_000)// 10 MB限制
        returnBadRequest("File too large");

    var memory =newMemory<byte>(newbyte[file.Length]);
    
    using(var stream = file.OpenReadStream())
    {
        await stream.ReadAsync(memory);
    }

    awaitProcessUploadedFileAsync(memory);
    
    returnOk("File processed successfully");
}

privateasyncTaskProcessUploadedFileAsync(Memory<byte> fileData)
{
    // 模拟一些异步处理
    await Task.Delay();// 实际处理的占位符
    
    // 示例:统计非零字节数
    int nonZeroBytes =;
    foreach(byte b in fileData.Span)
    {
        if(b!=) nonZeroBytes++;
    }
    
    Console.WriteLine($"Processed file with {nonZeroBytes} non-zero bytes");
}

这个示例展示了如何使用Memory<T>在异步方法间处理文件数据,而无需不必要的复制操作。

2. 缓存大型对象

在ASP.NET应用程序中缓存大型对象时,Memory<T>会很有用:

代码语言:javascript代码运行次数:0运行复制
public classLargeObjectCache
{
    privateMemory<byte> cachedData;

    publicasyncTask<Memory<byte>>GetOrCreateAsync(Func<Task<byte[]>> createFunc)
    {
        if(cachedData.IsEmpty)
        {
            byte[] newData =awaitcreateFunc();
            cachedData =newMemory<byte>(newData);
        }
        return cachedData;
    }
}

// 在控制器中的用法
[HttpGet("large-data")]
publicasyncTask<IActionResult>GetLargeData([FromServices]LargeObjectCache cache)
{
    var data =await cache.GetOrCreateAsync(async()=>
    {
        // 模拟获取大型数据
        await Task.Delay();
        returnnewbyte[1_000_000];// 1 MB的数据
    });

    returnFile(data.ToArray(),"application/octet-stream");
}
3. 在后台任务中高效构建字符串

对于构建大型字符串的后台任务,Memory<T>可能比StringBuilder更高效:

代码语言:javascript代码运行次数:0运行复制
public classReportGenerator:BackgroundService
{
    protectedoverrideasyncTaskExecuteAsync(CancellationToken stoppingToken)
    {
        while(!stoppingToken.IsCancellationRequested)
        {
            awaitGenerateReportAsync();
            await Task.Delay(TimeSpan.FromHours(), stoppingToken);
        }
    }

    privateasyncTaskGenerateReportAsync()
    {
        var writer =newArrayBufferWriter<char>(initialCapacity:*);// 初始容量为1 MB

        awaitWriteReportHeaderAsync(writer);
        awaitWriteReportBodyAsync(writer);
        awaitWriteReportFooterAsync(writer);

        string report =newstring(writer.WrittenMemory.Span);
        awaitSaveReportAsync(report);
    }

    privateasyncTaskWriteReportHeaderAsync(IBufferWriter<char> writer)
{
        var memory = writer.GetMemory();
        int written = System.Text.Encoding.UTF8.GetBytes("Report Header\n", memory.Span);
        writer.Advance(written);
        await Task.Delay();// 模拟一些异步工作
    }

    // 用于WriteReportBodyAsync和WriteReportFooterAsync的类似方法

    privateasyncTaskSaveReportAsync(string report)
{
        // 将报告保存到数据库或文件
        await File.WriteAllTextAsync("report.txt", report);
    }
}

这个示例展示了如何在后台任务中结合使用Memory<T>IBufferWriter<T>来高效构建字符串,这在ASP.NET应用程序中对于诸如生成报告或数据处理之类的任务很常见。

对于希望优化应用程序的ASP.NET开发人员来说,Span<T>Memory<T>是强大的工具。通过使用这些类型,你可以编写更高效的代码,这些代码占用更少的内存且运行速度更快。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-30,如有侵权请联系 cloudcommunity@tencent 删除内存数据性能c#memory

本文标签: C 12 中的 SpanampltTampgt 和 MemoryampltTampgt高级开发人员的性能助推器