C# 提高大型(1-10 gb)文件流的速度.Net Core
我正在尝试使用多部分数据通过API上传*.iso文件,并将其流式传输到本地文件夹。 我使用了Stream.copyanc(destinationStream),它运行得很慢,但还不错。但现在我需要报告进展情况。因此,我使用定制的CopyTOAsync并向其中添加了一个进度报告。但是,即使与Stream::CopyToASync相比,该方法也非常慢(根本不可接受)C# 提高大型(1-10 gb)文件流的速度.Net Core,c#,asp.net-core,stream,multipartform-data,large-files,C#,Asp.net Core,Stream,Multipartform Data,Large Files,我正在尝试使用多部分数据通过API上传*.iso文件,并将其流式传输到本地文件夹。 我使用了Stream.copyanc(destinationStream),它运行得很慢,但还不错。但现在我需要报告进展情况。因此,我使用定制的CopyTOAsync并向其中添加了一个进度报告。但是,即使与Stream::CopyToASync相比,该方法也非常慢(根本不可接受) public async Task CopyToAsync(Stream source, Stream destination, l
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
- 根据平台的不同,省略
可能默认与UI线程(WPF、WinForms)或任何线程(ASP.NET核心)同步。如果它与流复制操作中的UI线程同步,那么性能大幅下降也就不足为奇了ConfigureAwait
- 如果在线程同步的上下文中运行代码,则
语句将被不必要地延迟,因为程序将继续调度到可能正忙的线程wait
- 根据平台的不同,省略
- 使用一个大小至少为几百KiB的缓冲区,甚至是用于异步操作的兆字节大小的缓冲区,而不是典型的4KiB或80KiB大小的阵列。
- 如果使用的是
请确保使用了FileStream
或FileOptions.Asynchronous
,否则useAsync:true
将通过使用线程池线程而不是Windows的本机异步IO执行阻塞IO来伪造其异步操作FileStream
Stream::CopyToAsync
,而不是自己重新实现它。如果您想要进度报告,那么考虑子类<代码>流<代码>(作为代理包装)。
下面是我将如何编写您的代码:
ProxyStream
添加对IProgress
的支持:FileOptions.Asynchronous | FileOptions.SequentialScan
创建任何FileStream
实例CopyToAsync
公共类ProxyStream:ProxyStream
{
私有只读IProgress进程;
私有只读Int64?总计;
public ProgressProxyStream(Stream Stream、IProgress ProxyStream、Open)
:基本(流,打开)
{
this.progress=progress??抛出新的ArgumentNullException(nameof(progress));
this.total=stream.CanSeek?stream.Length:(Int64?)null;
}
公共重写任务ReadAsync(字节[]缓冲区、Int32偏移量、Int32计数、CancellationToken CancellationToken)
{
此.progress.Report((抵销,此.total));
返回this.Stream.ReadAsync(缓冲区、偏移量、计数、取消令牌);
}
}
如果上述
ProgressProxyStream
仍会影响性能,那么我敢打赌瓶颈在IProgress.Report
回调目标(我假设它与UI线程同步)ProgressProxyStream(甚至是IProgress
的实现)内将进度报告转储到,而不阻止任何其他IO活动。这些文件是什么?您可以使用压缩流传输未压缩的文件。传输一个1GB的文件需要多长时间?您正在将这些文件上载到远程服务器吗?还是所有东西都留在你的本地网络上?我不想告诉你,但你的方法一开始就有缺陷。想要更快的性能?不要发送MULTIPARTFORM-DATA-那里的编码使您的数据从一开始就大30%,因为它必须是ASCII编码的。以searate请求(无格式)将其作为二进制内容进行流式处理。发送必要的元数据aeitehr之前(获取上传url)或作为标题。看看Youtube在他们的SDK中是如何做到的。@ChristophLütjen我怀疑ArrayPool
是否有助于考虑所讨论的代码没有在紧循环中分配数组。这是一个很好的建议,但考虑到它似乎没有考虑到这一点,它并不能真正解释“慢但不太坏”与“非常慢”的区别添加进度报告部分后,OP观察到的差异。他们还尝试了合理增加缓冲区大小。@CeeMcSharpface我敢打赌上下文中发生了阻塞线程同步。Report
call。@Dai,非常感谢。瓶颈实际上在报告方法内部。我感谢你的帮助。
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 );
}
}