c# - 提高流式传输大型 (1-10 GB) 文件的速度 .Net Core

标签 c# asp.net-core stream multipartform-data large-files

我正在尝试使用 multipartform-data 通过我的 API 上传 *.iso 文件并将它们流式传输到本地文件夹中。 我使用 Stream.CopyAsync(destinationStream) ,它运行缓慢,但还不错。但现在我需要报告进展情况。因此,我使用了自定义 CopyTOAsync 并向其添加了进度报告。但该方法非常慢(根本无法接受),即使与 Stream::CopyToASync 相比也是如此。

 public async Task CopyToAsync(Stream source, Stream destination, long? contentLength, ICommandContext context, int bufferSize = 81920 )
    {
        var buffer = new byte[bufferSize];
        int bytesRead;
        long totalRead = 0;
        while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await destination.WriteAsync(buffer, 0, bytesRead);
            totalRead += bytesRead;
            context.Report(CommandResources.RID_IMAGE_LOADIND, Math.Clamp((uint)((totalRead * 100) / contentLength), 3, 99));
        }
        _logger.Info($"Total read during upload : {totalRead}");
    }

我尝试过的: Stream::CopyToAsync 的默认缓冲区大小是 81920 字节,我首先使用相同的值,然后尝试将缓冲区大小增加到 104857600 字节 - 没有区别。

关于如何提高自定义 CopyToAsync 的性能,您还有其他想法吗?

最佳答案

  • 始终使用ConfigureAwait await为异步延续指定线程同步。
    • 根据平台不同,省略 ConfigureAwait可能默认与 UI 线程(WPF、WinForms)或任何线程(ASP.NET Core)同步。如果它与 Stream 复制操作中的 UI 线程同步,那么性能急剧下降也就不足为奇了。
    • 如果您在线程同步上下文中运行代码,那么您的 await语句将被不必要地延迟,因为程序将继续安排到可能很忙的线程。
  • 使用大小至少为几百 KiB 的缓冲区 - 甚至兆字节大小的缓冲区来进行异步操作 - 而不是典型的 4KiB 或 80KiB 大小的数组。
  • 如果您使用FileStream确保您使用了FileOptions.AsynchronoususeAsync: true否则FileStream将通过使用线程池线程而不是 Windows 的 native 异步 IO 执行阻塞 IO 来伪造其异步操作。

关于您的实际代码 - 只需使用 Stream::CopyToAsync而不是自己重新实现。如果您想要进度报告,请考虑子类化 Stream (作为代理包装器)。

这是我编写代码的方式:

  1. First, add my ProxyStream class from this GitHub Gist到您的项目。
  2. 然后子类ProxyStream添加对 IProgress 的支持:
  3. 确保任何FileStream实例是使用 FileOptions.Asynchronous | FileOptions.SequentialScan 创建的.
  4. 使用CopyToAsync .
public class ProgressProxyStream : ProxyStream
{
    private readonly IProgress<(Int64 soFar, Int64? total)> progress;
    private readonly Int64? total;

    public ProgressProxyStream( Stream stream, IProgress<Int64> progress, Boolean leaveOpen )
        : base( stream, leaveOpen ) 
    {
        this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
        this.total = stream.CanSeek ? stream.Length : (Int64?)null;
    }

    public override Task<Int32> ReadAsync( Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken )
    {
        this.progress.Report( ( offset, this.total ) );
        return this.Stream.ReadAsync( buffer, offset, count, cancellationToken );
    }
}

如果性能仍然受到上述影响 ProgressProxyStream那么我愿意打赌瓶颈就在 IProgress.Report 内部回调目标(我假设它与 UI 线程同步)- in which case a better solution is to use a ( System.Threading.Channels.Channel )对于ProgressProxyStream (或者甚至是 IProgress<T> 的实现)转储进度报告,而不阻塞任何其他 IO 事件。

关于c# - 提高流式传输大型 (1-10 GB) 文件的速度 .Net Core,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60772727/

相关文章:

c# - 我可以有一个基类,其中每个派生类都有自己的静态属性副本吗?

c# - 如何在 ASP .NET Core 中初始化主机之前读取配置设置?

ios - 如何以字典流形式获取输出 - 套接字

c - fgetc() 和/或 fputc() 不一致的原因

c# - Microsoft ml.net 连接 2 列作为标签

c# - 尝试使用 C# 中的 Guid 作为 Cassandra 中的主键

c# - 根据字符串中定义的数据类型在运行时生成结构

url-rewriting - ASP.NET Core 1.1 Url 重写 - www 到非 www

authentication - 在 Asp.Net Core Web 应用中使用 EasyAuth 对 Azure 应用服务上的 AAD 进行身份验证时,无法填充 ClaimsPrincipal

C++ sync_with_stdio 性能